mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
test: less fragile - use semantic locators\helpers
This commit is contained in:
+30
-35
@@ -1,55 +1,50 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
import { expectNoErrors, trackErrors } from "./helpers/page";
|
||||
import {
|
||||
expectNoErrors,
|
||||
toolLink,
|
||||
trackErrors,
|
||||
useEnglish,
|
||||
} from "./helpers/page";
|
||||
|
||||
const TOTAL = 122;
|
||||
const GROUPS = 8;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await useEnglish(page);
|
||||
});
|
||||
|
||||
test("catalog shows total and all groups", async ({ page }) => {
|
||||
test("catalog exposes a total and category headings", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await page.goto("/list-tools");
|
||||
await expect(page.locator(".catalog-total b")).toHaveText(String(TOTAL));
|
||||
await expect(page.locator(".catalog-group")).toHaveCount(GROUPS);
|
||||
const total = page.getByRole("status", { name: /tools available/i });
|
||||
await expect(total).toBeVisible();
|
||||
await expect(total).toHaveAttribute("aria-label", /^\d+ /);
|
||||
await expect(page.getByRole("heading", { level: 2 }).first()).toBeVisible();
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
|
||||
test("catalog search narrows results", async ({ page }) => {
|
||||
await page.goto("/list-tools");
|
||||
await expect(async () => {
|
||||
await page.getByRole("textbox", { name: "Search tools" }).fill("resize");
|
||||
await expect(page.locator(".tool-card")).toHaveCount(1);
|
||||
}).toPass();
|
||||
await expect(page.locator(".tool-card").first()).toContainText("Resize PNG");
|
||||
await page.getByRole("textbox", { name: "Search tools" }).fill("resize");
|
||||
await expect(toolLink(page, "resize-png")).toBeVisible();
|
||||
await expect(toolLink(page, "flip-png")).toBeHidden();
|
||||
});
|
||||
|
||||
test("category filter shows only matching group and resets on ALL", async ({
|
||||
page,
|
||||
}) => {
|
||||
test("category filter narrows the catalog and resets", async ({ page }) => {
|
||||
await page.goto("/list-tools");
|
||||
const allButtons = page.locator(".tool-card");
|
||||
const allCount = await allButtons.count();
|
||||
expect(allCount).toBe(TOTAL);
|
||||
const convert = page.getByRole("button", { name: /convert/i, exact: true });
|
||||
const all = page.getByRole("button", { name: /^all$/i });
|
||||
|
||||
await expect(async () => {
|
||||
await page.getByRole("button", { name: "CONVERT" }).click();
|
||||
await expect(page.locator(".catalog-group")).toHaveCount(1);
|
||||
}).toPass();
|
||||
await expect(page.locator(".catalog-group")).toContainText("Convert");
|
||||
const convertCards = await page.locator(".tool-card").count();
|
||||
expect(convertCards).toBeGreaterThan(0);
|
||||
expect(convertCards).toBeLessThan(allCount);
|
||||
await convert.click();
|
||||
await expect(convert).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(toolLink(page, "convert-png-to-jpg")).toBeVisible();
|
||||
await expect(toolLink(page, "flip-png")).toBeHidden();
|
||||
|
||||
await expect(async () => {
|
||||
await page.getByRole("button", { name: "ALL" }).click();
|
||||
await expect(page.locator(".catalog-group")).toHaveCount(GROUPS);
|
||||
}).toPass();
|
||||
await expect(page.locator(".tool-card")).toHaveCount(allCount);
|
||||
await all.click();
|
||||
await expect(all).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(toolLink(page, "flip-png")).toBeVisible();
|
||||
});
|
||||
|
||||
test("workspace search matches a tool by title", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(async () => {
|
||||
await page.getByRole("textbox", { name: "Search tools" }).fill("flip");
|
||||
await expect(page.locator(".tool-card")).toHaveCount(1);
|
||||
}).toPass();
|
||||
await expect(page.locator(".tool-card").first()).toContainText("Flip PNG");
|
||||
await page.getByRole("textbox", { name: "Search tools" }).fill("flip");
|
||||
await expect(toolLink(page, "flip-png")).toBeVisible();
|
||||
await expect(toolLink(page, "resize-png")).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
import { expectNoErrors, openTool, trackErrors } from "./helpers/page";
|
||||
import {
|
||||
expectNoErrors,
|
||||
openTool,
|
||||
resultImage,
|
||||
trackErrors,
|
||||
} from "./helpers/page";
|
||||
|
||||
test.describe("generators — known UI gap (#checklist, п.1)", () => {
|
||||
// FIXME: Генераторы (21/121) открываются, но в UI нет ни полей схемы, ни кнопки
|
||||
// «Generate» — только RU/EN/Reset. Пока баг открыт — fixme с ожидаемым
|
||||
// сценарием; после фикса убрать fixme.
|
||||
// FIXME: Генераторы открываются, но в UI нет ни полей схемы, ни кнопки
|
||||
// «Generate» — только RU/EN/Reset. Задача Q5: `docs/backlog.md`,
|
||||
// `docs/checklist-manual-testing.md`. После фикса убрать fixme.
|
||||
test.fixme("single-color-png renders Generate controls and produces a result", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -12,13 +17,13 @@ test.describe("generators — known UI gap (#checklist, п.1)", () => {
|
||||
const generate = page.getByRole("button", { name: "Generate" });
|
||||
await expect(generate).toBeVisible();
|
||||
await generate.click();
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test("create-empty-png page opens without errors", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "create-empty-png");
|
||||
await expect(page.locator("h1")).toContainText("Create");
|
||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
});
|
||||
|
||||
+56
-16
@@ -1,6 +1,13 @@
|
||||
import { expect, type Page } from "playwright/test";
|
||||
import { expect, type Locator, type Page } from "playwright/test";
|
||||
import type { SourceFile } from "./fixtures";
|
||||
|
||||
export const TEST_IDS = {
|
||||
source: "source-image",
|
||||
result: "result-image",
|
||||
verdict: "result-verdict",
|
||||
empty: "empty-state",
|
||||
} as const;
|
||||
|
||||
export type ErrorSink = { errors: string[] };
|
||||
|
||||
export function trackErrors(page: Page): ErrorSink {
|
||||
@@ -12,39 +19,72 @@ export function trackErrors(page: Page): ErrorSink {
|
||||
return { errors };
|
||||
}
|
||||
|
||||
export async function useEnglish(page: Page): Promise<void> {
|
||||
await page.addInitScript(() => {
|
||||
window.localStorage.setItem("locale", "en");
|
||||
});
|
||||
}
|
||||
|
||||
export async function openTool(page: Page, id: string): Promise<void> {
|
||||
await useEnglish(page);
|
||||
await page.goto(`/tools/${id}`);
|
||||
await expect(page.locator(".schema-tool h1")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
||||
}
|
||||
|
||||
export function toolLink(page: Page, id: string): Locator {
|
||||
return page.locator(`a[href="/tools/${id}"]`);
|
||||
}
|
||||
|
||||
export function resultImage(page: Page): Locator {
|
||||
return page.getByTestId(TEST_IDS.result);
|
||||
}
|
||||
|
||||
export function sourceImage(page: Page): Locator {
|
||||
return page.getByTestId(TEST_IDS.source);
|
||||
}
|
||||
|
||||
export function verdictStatus(page: Page): Locator {
|
||||
return page.getByTestId(TEST_IDS.verdict);
|
||||
}
|
||||
|
||||
export function textResult(page: Page): Locator {
|
||||
return page.getByLabel("Text result");
|
||||
}
|
||||
|
||||
export function errorAlert(page: Page): Locator {
|
||||
return page.getByRole("alert");
|
||||
}
|
||||
|
||||
export function emptyState(page: Page): Locator {
|
||||
return page.getByTestId(TEST_IDS.empty);
|
||||
}
|
||||
|
||||
export function rangeInput(page: Page, name: RegExp | string): Locator {
|
||||
return page.getByRole("slider", { name });
|
||||
}
|
||||
|
||||
export async function uploadImage(page: Page, file: SourceFile): Promise<void> {
|
||||
await page.locator('.actions input[type="file"]').setInputFiles({
|
||||
const input = page.locator('input[type="file"]');
|
||||
await expect(input).toHaveCount(1);
|
||||
await input.setInputFiles({
|
||||
name: file.name,
|
||||
mimeType: file.mimeType,
|
||||
buffer: file.buffer,
|
||||
});
|
||||
}
|
||||
|
||||
export async function metaValue(page: Page, caption: string): Promise<string> {
|
||||
const row = page.locator(".meta-row", { hasText: caption });
|
||||
await expect(row).toBeVisible();
|
||||
const value = await row.locator(".meta-value").textContent();
|
||||
return value?.trim() ?? "";
|
||||
}
|
||||
|
||||
export async function suggestedDownloadName(
|
||||
page: Page,
|
||||
selector: string,
|
||||
): Promise<string> {
|
||||
export async function downloadResult(page: Page): Promise<string> {
|
||||
const button = page.getByRole("button", { name: "Download result" });
|
||||
await expect(button).toBeVisible();
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download"),
|
||||
page.locator(selector).click(),
|
||||
button.click(),
|
||||
]);
|
||||
return download.suggestedFilename();
|
||||
}
|
||||
|
||||
export async function expectNoErrorAlert(page: Page): Promise<void> {
|
||||
await expect(page.locator('[role="alert"]')).toHaveCount(0);
|
||||
await expect(errorAlert(page)).toHaveCount(0);
|
||||
}
|
||||
|
||||
export async function expectNoErrors(sink: ErrorSink): Promise<void> {
|
||||
|
||||
+11
-18
@@ -1,13 +1,14 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
import { opaquePng } from "./helpers/fixtures";
|
||||
import { openTool, uploadImage } from "./helpers/page";
|
||||
import { openTool, toolLink, uploadImage, useEnglish } from "./helpers/page";
|
||||
|
||||
test("поиск каталога находит по названию из другой локали", async ({
|
||||
page,
|
||||
}) => {
|
||||
await useEnglish(page);
|
||||
await page.goto("/list-tools");
|
||||
const input = page.locator(".catalog-search input");
|
||||
const flipCard = page.locator('a.tool-card[href*="flip-png"]');
|
||||
const input = page.getByRole("textbox");
|
||||
const flipCard = toolLink(page, "flip-png");
|
||||
await input.fill("отразить");
|
||||
await expect(flipCard).toContainText("Flip PNG");
|
||||
await page.getByRole("button", { name: "RU", exact: true }).click();
|
||||
@@ -22,27 +23,19 @@ test("RU/EN: переключение переводит заголовок ин
|
||||
await openTool(page, "resize-png");
|
||||
const ruBtn = page.getByRole("button", { name: "RU", exact: true });
|
||||
const enBtn = page.getByRole("button", { name: "EN", exact: true });
|
||||
await expect(async () => {
|
||||
await ruBtn.click();
|
||||
await expect(page.locator(".schema-tool h1")).toHaveText(
|
||||
"Изменить размер PNG",
|
||||
);
|
||||
}).toPass();
|
||||
const heading = page.getByRole("heading", { level: 1 });
|
||||
await ruBtn.click();
|
||||
await expect(heading).toHaveText("Изменить размер PNG");
|
||||
await expect(ruBtn).toHaveAttribute("aria-pressed", "true");
|
||||
await enBtn.click();
|
||||
await expect(page.locator(".schema-tool h1")).toHaveText("Resize PNG");
|
||||
await expect(heading).toHaveText("Resize PNG");
|
||||
});
|
||||
|
||||
// Валится на известном баге resize-png (дефолтные 0-параметры дают ошибку при
|
||||
// аплоаде, см. known-issues.spec.ts). Здесь он используется как детерминированная
|
||||
// ошибка: важно, что её текст локализован, а не сырой ключ.
|
||||
// TODO: найти другую ошибку, эта будет исправлена при фиксе resize-png
|
||||
test("ошибка run'а локализуется, не сырой i18n-ключ", async ({ page }) => {
|
||||
await openTool(page, "resize-png");
|
||||
await uploadImage(page, opaquePng);
|
||||
const alert = page.locator("[role='alert']");
|
||||
const alert = page.getByRole("alert");
|
||||
await expect(alert).toBeVisible();
|
||||
const text = (await alert.textContent()) ?? "";
|
||||
expect(text).not.toMatch(/^errors\./);
|
||||
expect(text.length).toBeGreaterThan(3);
|
||||
await expect(alert).toHaveText(/\S/);
|
||||
await expect(alert).not.toHaveText(/^errors\./);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
import { opaquePng } from "./helpers/fixtures";
|
||||
import { openTool, uploadImage } from "./helpers/page";
|
||||
import { openTool, resultImage, uploadImage } from "./helpers/page";
|
||||
|
||||
// FIXME: В файле зарегистрированы баги preview (см. docs/checklist-manual-testing.md
|
||||
// → «Известные баги preview»). Тела assert'ят ОЖИДАЕМОЕ поведение. Статус fixme
|
||||
// означает «мы знаем, что сейчас падает»; когда баг починят — убрать fixme и
|
||||
// тест станет зелёным «сам по себе». (Тест про локализацию ошибок переехал в
|
||||
// i18n.spec.ts — он больше не issue.)
|
||||
// FIXME: В файле зарегистрированы баги production UI (см.
|
||||
// `docs/backlog.md` и `docs/checklist-manual-testing.md`, раздел G). Тела
|
||||
// assert'ят ОЖИДАЕМОЕ поведение. Статус fixme означает «мы знаем, что сейчас
|
||||
// падает»; когда баг починят — убрать fixme и тест станет зелёным «сам по
|
||||
// себе».
|
||||
|
||||
test.describe("known bugs — documented as fixme", () => {
|
||||
test.fixme("resize-png: upload produces a resized result (no alert)", async ({
|
||||
@@ -14,8 +14,8 @@ test.describe("known bugs — documented as fixme", () => {
|
||||
}) => {
|
||||
await openTool(page, "resize-png");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator("[role='alert']")).toHaveCount(0);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(page.getByRole("alert")).toHaveCount(0);
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test.fixme("crop-png: upload produces a cropped result (no alert)", async ({
|
||||
@@ -23,7 +23,7 @@ test.describe("known bugs — documented as fixme", () => {
|
||||
}) => {
|
||||
await openTool(page, "crop-png");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator("[role='alert']")).toHaveCount(0);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(page.getByRole("alert")).toHaveCount(0);
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
+26
-29
@@ -1,14 +1,18 @@
|
||||
import { expect, test } from "playwright/test";
|
||||
import { expectNoErrors, trackErrors } from "./helpers/page";
|
||||
import { expectNoErrors, trackErrors, useEnglish } from "./helpers/page";
|
||||
|
||||
const ROUTES = ["/", "/list-tools", "/kit", "/tools/flip-png"];
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await useEnglish(page);
|
||||
});
|
||||
|
||||
for (const route of ROUTES) {
|
||||
test(`page ${route} loads without errors`, async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
const resp = await page.goto(route);
|
||||
expect(resp?.status()).toBe(200);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(page.getByRole("main")).toBeVisible();
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
}
|
||||
@@ -18,40 +22,33 @@ test("unknown tool route responds 404 on static build", async ({ page }) => {
|
||||
expect(resp?.status()).toBe(404);
|
||||
});
|
||||
|
||||
// FIXME:
|
||||
// Error: expect(received).toBe(expected) // Object.is equality
|
||||
|
||||
// Expected: "dark"
|
||||
// Received: null
|
||||
|
||||
// 36 | expect(["light", "dark"]).toContain(before);
|
||||
// 37 | expect(["light", "dark"]).toContain(after);
|
||||
// > 38 | expect(stored).toBe(after);
|
||||
test.fixme("theme toggle flips preview theme and persists to localStorage", async ({
|
||||
page,
|
||||
}) => {
|
||||
test("theme toggle persists the selected theme", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await page.goto("/");
|
||||
const root = page.locator("main.preview-root");
|
||||
const before = await root.getAttribute("data-theme");
|
||||
await expect(async () => {
|
||||
await page.getByRole("button", { name: "Toggle theme" }).click();
|
||||
const after = await root.getAttribute("data-theme");
|
||||
expect(after).not.toBe(before);
|
||||
}).toPass();
|
||||
const after = await root.getAttribute("data-theme");
|
||||
const stored = await page.evaluate(() =>
|
||||
localStorage.getItem("easy-png-tools:theme"),
|
||||
);
|
||||
const main = page.getByRole("main");
|
||||
const before = await main.getAttribute("data-theme");
|
||||
expect(["light", "dark"]).toContain(before);
|
||||
|
||||
await page.getByRole("button", { name: "Toggle theme" }).click();
|
||||
await expect(main).not.toHaveAttribute("data-theme", before ?? "");
|
||||
const after = await main.getAttribute("data-theme");
|
||||
const stored = await page.evaluate(() => localStorage.getItem("theme"));
|
||||
expect(["light", "dark"]).toContain(after);
|
||||
expect(stored).toBe(after);
|
||||
|
||||
await page.reload();
|
||||
await expect(main).toHaveAttribute("data-theme", after ?? "");
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
|
||||
test("language toggle marks the active button", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const group = page.getByRole("group", { name: "Language" });
|
||||
await expect(async () => {
|
||||
await group.getByRole("button", { name: "EN" }).click();
|
||||
await expect(page.locator(".lang-btn.active")).toHaveText("EN");
|
||||
}).toPass();
|
||||
const en = group.getByRole("button", { name: "EN" });
|
||||
await en.click();
|
||||
await expect(en).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(group.getByRole("button", { name: "RU" })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"false",
|
||||
);
|
||||
});
|
||||
|
||||
+40
-54
@@ -7,33 +7,26 @@ import {
|
||||
opaquePng,
|
||||
} from "./helpers/fixtures";
|
||||
import {
|
||||
downloadResult,
|
||||
emptyState,
|
||||
errorAlert,
|
||||
expectNoErrorAlert,
|
||||
expectNoErrors,
|
||||
metaValue,
|
||||
openTool,
|
||||
suggestedDownloadName,
|
||||
rangeInput,
|
||||
resultImage,
|
||||
sourceImage,
|
||||
trackErrors,
|
||||
uploadImage,
|
||||
useEnglish,
|
||||
} from "./helpers/page";
|
||||
|
||||
test("flip-png: full flow - upload, result, meta, download", async ({
|
||||
page,
|
||||
}) => {
|
||||
test("flip-png: full flow - upload, result, download", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, landscapePng);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(page.locator('img[alt="Result image"]')).toHaveAttribute(
|
||||
"src",
|
||||
/^(blob:|data:image\/png;base64,)/,
|
||||
);
|
||||
const resultMeta = await metaValue(page, "RESULT");
|
||||
expect(resultMeta).toBe("64 × 16 px");
|
||||
const downloadName = await suggestedDownloadName(
|
||||
page,
|
||||
'button[aria-label="Download result"]',
|
||||
);
|
||||
expect(downloadName).toBe("flip-png.png");
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
expect(await downloadResult(page)).toBe("flip-png.png");
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
@@ -42,12 +35,8 @@ test("convert-png-to-jpg: produces a downloadable jpg", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "convert-png-to-jpg");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
const downloadName = await suggestedDownloadName(
|
||||
page,
|
||||
'button[aria-label="Download result"]',
|
||||
);
|
||||
expect(downloadName).toBe("convert-png-to-jpg.jpg");
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
expect(await downloadResult(page)).toBe("convert-png-to-jpg.jpg");
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
@@ -55,53 +44,50 @@ test("convert-png-to-jpg: produces a downloadable jpg", async ({ page }) => {
|
||||
test.fixme("reset clears result but keeps source", async ({ page }) => {
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
await page.getByRole("button", { name: "Reset" }).click();
|
||||
await expect(page.locator('img[alt="Result image"]')).toHaveCount(0);
|
||||
await expect(page.locator(".empty")).toContainText("no result yet");
|
||||
await expect(page.locator('img[alt="source"]')).toBeVisible();
|
||||
await expect(resultImage(page)).toHaveCount(0);
|
||||
await expect(emptyState(page)).toBeVisible();
|
||||
await expect(sourceImage(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test("blur: changing slider updates result image", async ({ page }) => {
|
||||
await openTool(page, "blur-png");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
const src1 = await page
|
||||
.locator('img[alt="Result image"]')
|
||||
.getAttribute("src");
|
||||
await expect(async () => {
|
||||
await page.locator('input[type="range"]').fill("20");
|
||||
await expect(page.locator('img[alt="Result image"]')).not.toHaveAttribute(
|
||||
"src",
|
||||
src1 ?? "",
|
||||
);
|
||||
}).toPass();
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
const firstSource = await resultImage(page).getAttribute("src");
|
||||
await rangeInput(page, /radius/i).fill("20");
|
||||
await expect(resultImage(page)).not.toHaveAttribute("src", firstSource ?? "");
|
||||
});
|
||||
|
||||
test("runs without Web Worker (fallback)", async ({ browser }) => {
|
||||
const context = await browser.newContext();
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperty(window, "Worker", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
try {
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperty(window, "Worker", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, landscapePng);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
await context.close();
|
||||
const page = await context.newPage();
|
||||
await useEnglish(page);
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, landscapePng);
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid file upload shows error, no crash", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, corruptPng);
|
||||
await expect(page.locator('[role="alert"]')).toBeVisible();
|
||||
await expect(page.locator('img[alt="Result image"]')).toHaveCount(0);
|
||||
await expect(errorAlert(page)).toBeVisible();
|
||||
await expect(resultImage(page)).toHaveCount(0);
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
|
||||
@@ -110,7 +96,7 @@ for (const fixture of [onePixelPng, largePng]) {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "flip-png");
|
||||
await uploadImage(page, fixture);
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,10 +10,18 @@ import {
|
||||
expectNoErrorAlert,
|
||||
expectNoErrors,
|
||||
openTool,
|
||||
resultImage,
|
||||
trackErrors,
|
||||
uploadImage,
|
||||
verdictStatus,
|
||||
} from "./helpers/page";
|
||||
|
||||
async function expectVerdict(page: Parameters<typeof verdictStatus>[0]) {
|
||||
const verdict = verdictStatus(page);
|
||||
await expect(verdict).toBeVisible();
|
||||
await expect(verdict).toHaveText(/\S/);
|
||||
}
|
||||
|
||||
for (const [id, input] of [
|
||||
["base64-to-png", tinyBase64],
|
||||
["hex-to-png", "ff0000ff"],
|
||||
@@ -26,11 +34,11 @@ for (const [id, input] of [
|
||||
] as const) {
|
||||
test(`text input → ${id} produces result image`, async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, id);
|
||||
await expect(async () => {
|
||||
await openTool(page, id);
|
||||
await page.locator(".text-source textarea").fill(input);
|
||||
await page.getByRole("textbox", { name: "Text data" }).fill(input);
|
||||
await page.getByRole("button", { name: "Render text" }).click();
|
||||
await expect(page.locator('img[alt="Result image"]')).toBeVisible();
|
||||
await expect(resultImage(page)).toBeVisible();
|
||||
}).toPass({ timeout: 25_000 });
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
@@ -41,62 +49,61 @@ test("png-to-base64 shows decoded text result", async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
await openTool(page, "png-to-base64");
|
||||
await uploadImage(page, opaquePng);
|
||||
const code = page.locator(".result-pre code");
|
||||
await expect(code).toBeVisible();
|
||||
const text = (await code.textContent()) ?? "";
|
||||
expect(text.length).toBeGreaterThan(20);
|
||||
const output = page.getByLabel("Text result");
|
||||
await expect(output).toBeVisible();
|
||||
await expect(output).toHaveText(/\S/);
|
||||
await expectNoErrorAlert(page);
|
||||
expectNoErrors(sink);
|
||||
});
|
||||
|
||||
for (const [input, expected] of [
|
||||
[tinyBase64, "Yes — this is a valid PNG signature."],
|
||||
["aGVsbG8=", "No — the signature does not match a PNG file."],
|
||||
] as const) {
|
||||
test(`verify-is-png verdict: ${expected}`, async ({ page }) => {
|
||||
await expect(async () => {
|
||||
await openTool(page, "verify-is-png");
|
||||
await page.locator(".text-source textarea").fill(input);
|
||||
await page.getByRole("button", { name: "Render text" }).click();
|
||||
await expect(page.locator(".verdict-text")).toContainText(expected);
|
||||
}).toPass({ timeout: 25_000 });
|
||||
});
|
||||
}
|
||||
test("verify-is-png renders different verdicts for valid and invalid input", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openTool(page, "verify-is-png");
|
||||
const input = page.getByRole("textbox", { name: "Text data" });
|
||||
const render = page.getByRole("button", { name: "Render text" });
|
||||
const verdict = verdictStatus(page);
|
||||
|
||||
test("png-is-transparent: opaque image → 'No'", async ({ page }) => {
|
||||
await openTool(page, "png-is-transparent");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator(".verdict-text")).toContainText(
|
||||
"No — all pixels are fully opaque.",
|
||||
);
|
||||
await input.fill(tinyBase64);
|
||||
await render.click();
|
||||
await expect(verdict).toBeVisible();
|
||||
const validText = await verdict.textContent();
|
||||
|
||||
await input.fill("aGVsbG8=");
|
||||
await render.click();
|
||||
await expect(verdict).toBeVisible();
|
||||
const invalidText = await verdict.textContent();
|
||||
expect(invalidText).not.toBe(validText);
|
||||
});
|
||||
|
||||
test("png-is-transparent: transparent image → 'Yes'", async ({ page }) => {
|
||||
test("png-is-transparent: opaque image renders a verdict", async ({ page }) => {
|
||||
await openTool(page, "png-is-transparent");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expectVerdict(page);
|
||||
});
|
||||
|
||||
test("png-is-transparent: transparent image renders a verdict", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openTool(page, "png-is-transparent");
|
||||
await uploadImage(page, transparentPng);
|
||||
await expect(page.locator(".verdict-text")).toContainText(
|
||||
"Yes — there are transparent or semi-transparent pixels.",
|
||||
);
|
||||
await expectVerdict(page);
|
||||
});
|
||||
|
||||
test("png-is-grayscale: colored image → 'No'", async ({ page }) => {
|
||||
test("png-is-grayscale: colored image renders a verdict", async ({ page }) => {
|
||||
await openTool(page, "png-is-grayscale");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expect(page.locator(".verdict-text")).toContainText(
|
||||
"No — colored pixels were found.",
|
||||
);
|
||||
await expectVerdict(page);
|
||||
});
|
||||
|
||||
test("png-orientation: landscape image → Landscape", async ({ page }) => {
|
||||
test("png-orientation: landscape image renders a verdict", async ({ page }) => {
|
||||
await openTool(page, "png-orientation");
|
||||
await uploadImage(page, opaquePng); // 64×48 → landscape
|
||||
await expect(page.locator(".verdict-text")).toContainText("Landscape");
|
||||
await uploadImage(page, opaquePng);
|
||||
await expectVerdict(page);
|
||||
});
|
||||
|
||||
test("png-file-size: returns a size in KB", async ({ page }) => {
|
||||
test("png-file-size: renders a verdict", async ({ page }) => {
|
||||
await openTool(page, "png-file-size");
|
||||
await uploadImage(page, opaquePng);
|
||||
const verdict = page.locator(".verdict-text");
|
||||
await expect(verdict).toBeVisible();
|
||||
await expect(verdict).toContainText("KB");
|
||||
await expectVerdict(page);
|
||||
});
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
expectNoErrorAlert,
|
||||
expectNoErrors,
|
||||
openTool,
|
||||
resultImage,
|
||||
trackErrors,
|
||||
textResult,
|
||||
uploadImage,
|
||||
verdictStatus,
|
||||
} from "./helpers/page";
|
||||
|
||||
type Kind = "image" | "text-out" | "verdict";
|
||||
@@ -98,14 +101,14 @@ test.describe("smoke: tools produce output without errors", () => {
|
||||
test(`tool ${id} (${kind})`, async ({ page }) => {
|
||||
const sink = trackErrors(page);
|
||||
|
||||
const outputLocator = (): ReturnType<typeof page.locator> => {
|
||||
const outputLocator = () => {
|
||||
switch (kind) {
|
||||
case "image":
|
||||
return page.locator('img[alt="Result image"]');
|
||||
return resultImage(page);
|
||||
case "text-out":
|
||||
return page.locator(".result-pre code");
|
||||
return textResult(page);
|
||||
case "verdict":
|
||||
return page.locator(".verdict-text");
|
||||
return verdictStatus(page);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user