import type { Page } from "@playwright/test"; import { raiseTicket } from "../../../helpers/factories"; import { expect, test, uid, usePersona } from "../../../helpers/fixtures"; import { personas } from "../../../helpers/personas"; import { createArticle, createCategory } from "../../../helpers/portal"; usePersona("customer"); function ticketRows(page: Page) { return page.getByRole("button", { name: /^\d{4} / }); } test("an article without headings scrolls without errors", async ({ page, api }) => { const category = await createCategory(api); const article = await createArticle(api, category.name, { content: Array.from({ length: 60 }, (_, i) => `
Line ${i}
`).join(""), }); const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await page.goto(`/help/articles/${article.name}`); await page.getByText("Line 30", { exact: true }).hover(); await page.mouse.wheel(0, 2000); await expect(page.getByText("Line 59", { exact: true })).toBeInViewport(); expect(errors).toEqual([]); }); test("Back after creating a ticket returns to the list", async ({ page }) => { await page.goto("/help/my-tickets"); await page.getByRole("button", { name: "Raise a ticket" }).click(); await page.getByPlaceholder("A short description").fill(`E2E back ${uid()}`); await page.locator(".ProseMirror").fill("Back should skip the form"); await page.getByRole("button", { name: "Create ticket" }).click(); await expect(page).toHaveURL(/\/help\/my-tickets\/\d+$/); await page.goBack(); await expect(page).toHaveURL(/\/help\/my-tickets$/); }); test("closing a ticket confirms with a uniquely named button", async ({ page, api, apiAs }) => { const ticket = await raiseTicket(await apiAs("customer")); await page.goto(`/help/my-tickets/${ticket.name}`); await page.getByRole("button", { name: "Close", exact: true }).click(); const dialog = page.getByRole("dialog"); // the dialog's X keeps the name Close await expect(dialog.getByRole("button", { name: "Close", exact: true })).toHaveCount(1); await dialog.getByRole("button", { name: "Close ticket" }).click(); await expect.poll(async () => (await api.get("OD Ticket", ticket.name)).status).toBe("Closed"); }); test("tables and code blocks in a description are styled as on the desk", async ({ page, apiAs }) => { const subject = `E2E ${uid()} styled`; const ticket = await (await apiAs("customer")).call("opendesk.opendesk.doctype.od_ticket.api.new", { doc: { subject, description: "| Head |
|---|
| Cell |
let a = 1",
},
});
await page.goto(`/help/my-tickets/${ticket.name}`);
const frame = page.frameLocator("iframe").first();
await expect(frame.getByText("let a = 1")).toBeVisible();
// desk's .prose-f: a grey code block and a rule under the header row
await expect(frame.locator("pre")).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(frame.locator("tr").first()).not.toHaveCSS("border-bottom-width", "0px");
});
test("Load More rows survive opening a ticket and going back", async ({ page, api }) => {
const token = uid();
// as admin: the portal's create endpoint is rate limited per user
for (let i = 0; i < 22; i++)
await api.insert("OD Ticket", { subject: `${token} more ${i}`, raised_by: personas.customer.email });
await page.goto("/help/my-tickets");
await expect(ticketRows(page).first()).toBeVisible();
const reloaded = page.waitForRequest(
(request) => /frappe.client.get_count/.test(request.url()) && (request.postData() || "").includes(token)
);
await page.getByRole("textbox", { name: "Subject" }).fill(token);
await reloaded;
// a Load More before the filtered page lands appends to the unfiltered one
await expect(ticketRows(page).filter({ hasNotText: token })).toHaveCount(0);
await expect(ticketRows(page)).toHaveCount(20);
await page.getByRole("button", { name: "Load More" }).click();
await expect(ticketRows(page)).toHaveCount(22);
await ticketRows(page).last().click();
await expect(page).toHaveURL(/\/help\/my-tickets\/\d+$/);
await page.goBack();
await expect(ticketRows(page)).toHaveCount(22);
});
test("ticket details show a tooltip only for text that is cut off", async ({ page, ticket }) => {
const longName = `Acme ${"Holdings ".repeat(12)}Ltd`;
await page.route("**/api/method/opendesk.opendesk.doctype.od_ticket.api.get_one*", async (route) => {
const response = await route.fetch();
const body = await response.json();
body.message.customer = longName;
await route.fulfill({ response, json: body });
});
await page.goto(`/help/my-tickets/${ticket.name}`);
// A tooltip repeats its text, so a second copy means it opened.
const priority = page.getByText("Priority", { exact: true });
await priority.hover();
await page.waitForTimeout(800); // past the tooltip's 500ms delay
await expect(priority).toHaveCount(1);
await page.getByText(longName).first().hover();
await expect.poll(() => page.getByText(longName, { exact: true }).count()).toBeGreaterThan(1);
});