mirror of
https://github.com/penpot/penpot.git
synced 2026-11-03 00:20:41 +00:00
* ♻️ Use DS icons in the copy button The shared copy button (Inspect attribute rows and code blocks, share link, integrations) draws its clipboard and tick icons with the DS icons at the small size, colored through the color property. The button itself stays: it wraps whole rows. The icons are the same drawings, so nothing changes on screen. A visual test covers an Inspect computed attribute row and the code block copy button, at rest, on hover and after copying. AI-assisted-by: claude-opus-5-5 * ♻️ Move copy button styles off the deprecated styles The copy button stylesheet no longer imports refactor/: the button-style, flex-center and copy-wrapper-base mixins are inlined with DS values, the tertiary button and icon color aliases resolve to their DS tokens, and physical properties become logical ones. No visual change (whole viewer visual spec compared; the code tab syntax highlighting loads late on a cold first run, with either stylesheet). AI-assisted-by: claude-opus-5-5
445 lines
13 KiB
JavaScript
445 lines
13 KiB
JavaScript
import { test, expect } from "@playwright/test";
|
|
import { ViewerPage } from "../pages/ViewerPage";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await ViewerPage.init(page);
|
|
});
|
|
|
|
const singleBoardFileId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb1";
|
|
const singleBoardPageId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb2";
|
|
|
|
test("User goes to an empty Viewer", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupEmptyFile();
|
|
|
|
await viewerPage.goToViewer();
|
|
|
|
await expect(viewerPage.page.getByTestId("penpot-logo-link")).toBeVisible();
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User goes to the Viewer", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithSingleBoard();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await expect(viewerPage.page.getByTestId("penpot-logo-link")).toBeVisible();
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User goes to the Viewer and opens zoom modal", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithSingleBoard();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.page.getByTitle("Zoom").click();
|
|
|
|
await expect(viewerPage.page.getByTestId("penpot-logo-link")).toBeVisible();
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User goes to the Viewer Comments", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.showComments();
|
|
await viewerPage.showCommentsThread(1);
|
|
await expect(
|
|
viewerPage.page.getByRole("button", { name: "Post" }),
|
|
).toBeVisible();
|
|
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User opens Viewer comment list", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.showComments();
|
|
await viewerPage.page.getByTestId("viewer-comments-dropdown").click();
|
|
|
|
await viewerPage.page.getByText("Show comments list").click();
|
|
|
|
await expect(
|
|
viewerPage.page.getByRole("button", { name: "Show all comments" }),
|
|
).toBeVisible();
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User goes to the Viewer Inspect code", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.showCode();
|
|
|
|
await expect(
|
|
viewerPage.page.getByRole("button", {
|
|
name: "Toggle panel Size and position",
|
|
}),
|
|
).toBeVisible();
|
|
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User goes to the Viewer Inspect code, code tab", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.showCode();
|
|
await viewerPage.page
|
|
.getByRole("combobox")
|
|
.filter({ hasText: "Styles" })
|
|
.click();
|
|
await viewerPage.page.getByRole("option", { name: "Code" }).click();
|
|
|
|
await expect(
|
|
viewerPage.page.getByRole("button", { name: "Copy all code" }),
|
|
).toBeVisible();
|
|
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test("User opens Share modal", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithSingleBoard();
|
|
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
await viewerPage.page.getByRole("button", { name: "Share" }).click();
|
|
|
|
await expect(
|
|
viewerPage.page.getByRole("button", { name: "Get link" }),
|
|
).toBeVisible();
|
|
await expect(viewerPage.page).toHaveScreenshot();
|
|
});
|
|
|
|
test.describe("Thumbnails panel", () => {
|
|
test("Shows the thumbnails panel collapsed and expanded", async ({
|
|
page,
|
|
}) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithMultipleBoards();
|
|
await viewerPage.goToViewer({
|
|
fileId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0",
|
|
pageId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3",
|
|
});
|
|
|
|
await page.locator("#current-frame").click();
|
|
const panel = page.locator("section").filter({ hasText: /boards/i });
|
|
await expect(panel).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Collapsed: the summary with its expand and close buttons.
|
|
await expect(panel).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
// The scroll handlers only show on hover.
|
|
await panel.locator("button").nth(3).hover();
|
|
await expect(panel).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await panel.locator("button").first().click();
|
|
await page.mouse.move(640, 700);
|
|
// Expanded: the expand arrow points up.
|
|
await expect(panel).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Comments filter", () => {
|
|
test("Shows the comments filter closed and open", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
await viewerPage.showComments();
|
|
|
|
const trigger = page.getByTestId("viewer-comments-dropdown");
|
|
await expect(trigger).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Closed: the "Comments" trigger with its arrow.
|
|
await expect(trigger).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await trigger.click();
|
|
await expect(page.getByText("Show comments list")).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Open: the filter menu with the selected option ticked.
|
|
const menu = page.getByText("Show comments list").locator("../..");
|
|
await expect(menu).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Board navigation", () => {
|
|
test("Shows the previous, next and first board buttons", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithMultipleBoards();
|
|
await viewerPage.goToViewer({
|
|
fileId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0",
|
|
pageId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3",
|
|
});
|
|
|
|
const next = page.getByRole("button", { name: "Next" });
|
|
await expect(next).toBeVisible();
|
|
await page.mouse.move(640, 300);
|
|
// Wait for the slide-in transition of the controls.
|
|
await page.waitForTimeout(800);
|
|
// First board: the next and first board buttons.
|
|
await expect(page).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await next.hover();
|
|
await expect(next).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await next.click();
|
|
const previous = page.getByRole("button", { name: "Previous" });
|
|
await previous.hover();
|
|
await expect(previous).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
const first = page.locator("[class*='viewer-bottom'] button");
|
|
await first.hover();
|
|
await expect(first).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Viewer header", () => {
|
|
test("Shows the header buttons and its menus", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
|
|
const header = page.locator("header").first();
|
|
await expect(header).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Interactions section.
|
|
await expect(header).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
// Zoom menu.
|
|
await page.getByTitle("Zoom").click();
|
|
await expect(page.getByText("Fit - Scale down to fit")).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
await expect(page).toHaveScreenshot({
|
|
clip: { x: 760, y: 0, width: 520, height: 260 },
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
await page.keyboard.press("Escape");
|
|
await page.mouse.click(640, 600);
|
|
|
|
// Sitemap menu.
|
|
await page
|
|
.locator("#current-frame")
|
|
.locator("..")
|
|
.locator("div")
|
|
.first()
|
|
.click();
|
|
await page.mouse.move(640, 600);
|
|
await expect(page).toHaveScreenshot({
|
|
clip: { x: 0, y: 0, width: 520, height: 200 },
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
await page.mouse.click(640, 600);
|
|
|
|
// Comments section, with its button selected.
|
|
await viewerPage.showComments();
|
|
await page.mouse.move(640, 600);
|
|
await expect(header).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Inspect code tab", () => {
|
|
test("Shows the code tab buttons, expanded and collapsed", async ({
|
|
page,
|
|
}) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
await viewerPage.showCode();
|
|
await page.getByRole("combobox").filter({ hasText: "Styles" }).click();
|
|
await page.getByRole("option", { name: "Code" }).click();
|
|
|
|
const copyAll = page.getByRole("button", { name: "Copy all code" });
|
|
await expect(copyAll).toBeVisible();
|
|
// The CSS block header, with its toggle, expand and copy buttons.
|
|
const cssToggle = page.locator('button[data-type="css"]');
|
|
const cssHeader = cssToggle.locator("..");
|
|
await page.mouse.move(0, 0);
|
|
await expect(copyAll).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
await expect(cssHeader).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await cssToggle.click();
|
|
await page.mouse.move(0, 0);
|
|
// Collapsed: the arrow turns.
|
|
await expect(cssHeader).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Interactions menu", () => {
|
|
test("Shows the interactions menu closed and open", async ({ page }) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithMultipleBoards();
|
|
await viewerPage.goToViewer({
|
|
fileId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0",
|
|
pageId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3",
|
|
});
|
|
|
|
const trigger = page
|
|
.getByText("Interactions", { exact: true })
|
|
.locator("..");
|
|
await expect(trigger).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Closed: the "Interactions" trigger with its arrow.
|
|
await expect(trigger).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
|
|
await trigger.click();
|
|
const menu = page.getByText("Show interactions", { exact: true });
|
|
await expect(menu).toBeVisible();
|
|
await page.mouse.move(640, 600);
|
|
// Open: the menu with the selected mode ticked.
|
|
await expect(menu.locator("../..")).toHaveScreenshot({
|
|
maxDiffPixelRatio: 0,
|
|
threshold: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Copy buttons", () => {
|
|
const shot = { maxDiffPixelRatio: 0, threshold: 0 };
|
|
|
|
const openInspectTab = async (page, name) => {
|
|
const viewerPage = new ViewerPage(page);
|
|
await viewerPage.setupLoggedInUser();
|
|
await viewerPage.setupFileWithComments();
|
|
await viewerPage.goToViewer({
|
|
fileId: singleBoardFileId,
|
|
pageId: singleBoardPageId,
|
|
});
|
|
await viewerPage.showCode();
|
|
await page.getByRole("combobox").filter({ hasText: "Styles" }).click();
|
|
await page.getByRole("option", { name }).click();
|
|
};
|
|
|
|
test("Computed attribute rows", async ({ page }) => {
|
|
await openInspectTab(page, "Computed");
|
|
const row = page.locator("[class*='copy-wrapper']").first();
|
|
await expect(row).toBeVisible();
|
|
await page.mouse.move(10, 700);
|
|
await expect(row).toHaveScreenshot(shot);
|
|
|
|
await row.hover();
|
|
await expect(row).toHaveScreenshot(shot);
|
|
|
|
await row.click();
|
|
await expect(row).toHaveAttribute("data-active", "true");
|
|
await expect(row).toHaveScreenshot(shot);
|
|
});
|
|
|
|
test("Code block copy button", async ({ page }) => {
|
|
await openInspectTab(page, "Code");
|
|
const copy = page.locator("[class*='css-copy-btn']");
|
|
await expect(copy).toBeVisible();
|
|
await page.mouse.move(10, 700);
|
|
await expect(copy).toHaveScreenshot(shot);
|
|
|
|
await copy.hover();
|
|
await expect(copy).toHaveScreenshot(shot);
|
|
|
|
await copy.click();
|
|
await expect(copy).toHaveAttribute("data-active", "true");
|
|
await expect(copy).toHaveScreenshot(shot);
|
|
});
|
|
});
|