import type { Page } from "@playwright/test"; import type { Api } from "../../../helpers/api"; import { expect, test, uid, usePersona } from "../../../helpers/fixtures"; import { raiseTicket } from "../../../helpers/factories"; import { addComment, openTicket, runTicketMethod } from "../../../helpers/ticket"; usePersona("agent"); const communications = (api: Api, ticket: string) => api.list("Communication", { fields: ["name", "content"], filters: { reference_doctype: "OD Ticket", reference_name: ticket }, }); type Ticket = { name: string; subject: string }; /** Merge `source` into `target` through the ticket's More menu. */ async function mergeFromUi(page: Page, source: Ticket, target: Ticket) { const opened = page.context().waitForEvent("page"); await openMergeDialog(page, source, target); await page.getByRole("button", { name: `Merge with ticket #${target.name}` }).click(); await expect(page.getByText("Ticket merged successfully.")).toBeVisible(); return opened; } async function openMergeDialog(page: Page, source: Ticket, target: Ticket) { await openTicket(page, source.name); await page.getByRole("banner").getByRole("button", { name: "More actions" }).click(); await page.getByRole("menuitem", { name: "Merge Ticket" }).click(); const dialog = page.getByRole("dialog"); await dialog.getByRole("button", { name: "Ticket", exact: true }).click(); await page.getByRole("combobox", { name: "Select Ticket" }).fill(target.subject); await page.getByRole("option", { name: `${target.subject} ${target.name},` }).click(); return dialog; } test("a ticket merges into another from the same customer", async ({ page, api, apiAs }) => { const customer = await apiAs("customer"); const target = await raiseTicket(customer); const source = await raiseTicket(customer); const opened = await mergeFromUi(page, source, target); expect((await opened).url()).toContain(`/opendesk/tickets/${target.name}`); expect(await api.get("OD Ticket", source.name)).toMatchObject({ is_merged: 1, merged_with: target.name, status: "Closed", }); }); test("a merge whose response is lost closes the dialog once the ticket shows merged", async ({ page, api, apiAs, }) => { const customer = await apiAs("customer"); const target = await raiseTicket(customer); const source = await raiseTicket(customer); // The server merges, but the browser gets a gateway timeout instead of the reply. await page.route("**/api/method/opendesk.opendesk.doctype.od_ticket.api.merge_ticket", async (route) => { await route.fetch(); await route.fulfill({ status: 504, body: "Gateway Timeout" }); }); const dialog = await openMergeDialog(page, source, target); await dialog.getByRole("button", { name: `Merge with ticket #${target.name}` }).click(); await expect(page.getByText("Could not confirm the merge. Reloading the ticket.")).toBeVisible(); await expect(dialog).toBeHidden(); expect((await api.get("OD Ticket", source.name)).is_merged).toBe(1); }); test("merging carries the source emails over to the target", async ({ page, api, apiAs }) => { const customer = await apiAs("customer"); const target = await raiseTicket(customer); const source = await raiseTicket(customer); await mergeFromUi(page, source, target); const moved = (await communications(api, target.name)).map((c) => c.content).join(); expect(moved).toContain(`${source.subject} description`); }); test("a later email splits off into a new ticket", async ({ page, api, apiAs }) => { const customer = await apiAs("customer"); const ticket = await raiseTicket(customer); await runTicketMethod(customer, ticket.name, "create_communication_via_contact", { message: "
Also, a separate problem
", }); const followUp = (await communications(api, ticket.name)).find((c) => c.content.includes("separate problem") )!; const subject = `Split off ${uid()}`; await openTicket(page, ticket.name); const row = page.locator(`[id="email:${followUp.name}"]`); await row.getByRole("button").last().click(); await page.getByRole("menuitem", { name: "Split Ticket" }).click(); const dialog = page.getByRole("dialog", { name: "Split ticket" }); await dialog.getByPlaceholder("Add a subject for the new ticket").fill(subject); await dialog.getByRole("button", { name: "Split into new ticket" }).click(); await expect.poll(() => api.exists("OD Ticket", { subject })).toBeTruthy(); const split = await api.exists("OD Ticket", { subject }); expect((await communications(api, split!)).map((c) => c.name)).toEqual([followUp.name]); expect((await communications(api, ticket.name)).map((c) => c.name)).not.toContain(followUp.name); }); test("next and previous ticket show that ticket's own timeline", async ({ api, apiAs, pageAs }) => { const page = await pageAs("agent2"); const agent = await apiAs("agent"); const customer = await apiAs("customer"); const notes = new Map