IKHSAN
ENID Baca dalam Bahasa Indonesia

opendesk

Public
Branch
main
Latest commit
08d9f3328 October 2026

form-script.spec.ts

Open raw
import type { Page } from "@playwright/test";
import { expect, test } from "../../helpers/fixtures";
import {
  FormScripts,
  TEMPLATE,
  clicksOn,
  countingAction,
  ensureTemplate,
} from "../../helpers/form-script";

const NEW_WITH_TEMPLATE = `/opendesk/tickets/new/${encodeURIComponent(TEMPLATE)}`;

let scripts: FormScripts;

test.beforeEach(({ api }) => {
  scripts = new FormScripts(api);
});

test.afterEach(async () => {
  await scripts.removeAll();
});

test("a flat action fires exactly once on each page", async ({ pageAs, ticket }) => {
  await scripts.create(countingAction("E2E Agent Ticket"));
  await scripts.create(countingAction("E2E Portal Ticket"), { portal: true });
  await scripts.create(countingAction("E2E Agent New"), { newPage: true });

  const surfaces = [
    { persona: "agent", url: `/opendesk/tickets/${ticket.name}`, label: "E2E Agent Ticket" },
    { persona: "customer", url: `/help/my-tickets/${ticket.name}`, label: "E2E Portal Ticket" },
    { persona: "agent", url: "/opendesk/tickets/new", label: "E2E Agent New" },
  ] as const;
  for (const surface of surfaces) {
    const page = await pageAs(surface.persona);
    await page.goto(surface.url);
    await page.getByRole("button", { name: surface.label }).click();
    await expect.poll(() => clicksOn(page)).toBe(1);
  }
});

test("grouped actions render, including the legacy items shape", async ({ pageAs, ticket }) => {
  await scripts.create(
    countingAction(
      "E2E Flat",
      `, { group: "E2E Group", options: [{ label: "E2E Option", onClick: () => {} }] },
      { group: "E2E Legacy", items: [{ label: "E2E Legacy Item", onClick: () => {
        window.__e2eClicks = (window.__e2eClicks || 0) + 1;
      } }] },
      { group: "E2E Labelled", buttonLabel: "E2E Menu", label: "E2E Menu Item", onClick: () => {} }`
    )
  );
  const page = await pageAs("agent");
  await page.goto(`/opendesk/tickets/${ticket.name}`);

  await page.getByRole("button", { name: "E2E Menu" }).click();
  await expect(page.getByRole("menuitem", { name: "E2E Menu Item" })).toBeVisible();
  await page.keyboard.press("Escape");

  await openMoreMenu(page);
  await expect(page.getByRole("menuitem", { name: "E2E Option" })).toBeVisible();
  await page.getByRole("menuitem", { name: "E2E Legacy Item" }).click();
  await expect.poll(() => clicksOn(page)).toBe(1);
});

test("actions can update fields, call the server, open dialogs and route", async ({
  api,
  pageAs,
  ticket,
}) => {
  await scripts.create(`return { actions: [
    { label: "E2E Set Priority", onClick: () => ctx.updateField("priority", "High") },
    { label: "E2E Call", onClick: () => ctx.call("frappe.client.get_count", { doctype: "OD Ticket Priority" })
      .then((count) => { window.__e2eCount = count; }) },
    { label: "E2E Dialog", onClick: () => ctx.$dialog({ title: "E2E Dialog Title", message: "From a form script" }) },
    { label: "E2E Route", onClick: () => ctx.router.push({ name: "TicketsAgent" }) },
  ] };`);
  const page = await pageAs("agent");
  await page.goto(`/opendesk/tickets/${ticket.name}`);

  await page.getByRole("button", { name: "E2E Set Priority" }).click();
  await expect.poll(async () => (await api.get("OD Ticket", ticket.name)).priority).toBe("High");

  await page.getByRole("button", { name: "E2E Call" }).click();
  await expect.poll(() => page.evaluate(() => (window as any).__e2eCount)).toBeGreaterThan(0);

  await page.getByRole("button", { name: "E2E Dialog" }).click();
  await expect(page.getByRole("dialog").getByText("E2E Dialog Title")).toBeVisible();
  await page.keyboard.press("Escape");

  await page.getByRole("button", { name: "E2E Route" }).click();
  await expect(page).toHaveURL(/\/opendesk\/tickets$/);
});

test("toast shows a message", async ({ pageAs, ticket }) => {
  await scripts.create(`return { actions: [
    { label: "E2E Toast", onClick: () => ctx.toast.success("E2E plain toast") },
  ] };`);
  const page = await pageAs("agent");
  await page.goto(`/opendesk/tickets/${ticket.name}`);

  await page.getByRole("button", { name: "E2E Toast" }).click();
  await expect(page.getByText("E2E plain toast", { exact: true })).toBeVisible();
});

test("createToast reads duration as seconds", async ({ pageAs, ticket }) => {
  // Form scripts pass seconds; vue-sonner reads milliseconds.
  await scripts.create(`return { actions: [
    { label: "E2E Timed Toast", onClick: () => ctx.createToast({ message: "E2E timed toast", type: "success", duration: 5 }) },
  ] };`);
  const page = await pageAs("agent");
  await page.goto(`/opendesk/tickets/${ticket.name}`);

  await page.getByRole("button", { name: "E2E Timed Toast" }).click();
  await expect(page.getByText("E2E timed toast", { exact: true })).toBeVisible();
  // A 5 second toast must still be up after 1.5s; read as ms it is long gone.
  await page.waitForTimeout(1500);
  await expect(page.getByText("E2E timed toast", { exact: true })).toBeVisible();
});

test.describe("new ticket form", () => {
  test.beforeEach(async ({ api }) => {
    await ensureTemplate(api);
  });

  test("onChange narrows a dependent field with applyFilters", async ({ pageAs }) => {
    await scripts.create(
      `return { actions: [], onChange: { e2e_trigger: (value) =>
        ctx.applyFilters("e2e_choice", value === "Show" ? ["Alpha", "Beta"] : null) } };`,
      { newPage: true }
    );
    const page = await pageAs("agent");
    await page.goto(NEW_WITH_TEMPLATE);

    await pick(page, "E2E trigger", "Show");
    await field(page, "E2E choice").getByRole("combobox").click();
    await expect(page.getByRole("option")).toHaveText(["Alpha", "Beta"]);
    await page.keyboard.press("Escape");

    await pick(page, "E2E trigger", "Hide");
    await expect(field(page, "E2E choice").getByRole("combobox")).toBeDisabled();
  });

  test("depends_on and mandatory_depends_on follow other values", async ({ pageAs }) => {
    const page = await pageAs("agent");
    await page.goto(NEW_WITH_TEMPLATE);
    await expect(field(page, "E2E shown")).toHaveCount(0);
    await expect(field(page, "E2E needed").getByText("*")).toHaveCount(0);

    await pick(page, "E2E trigger", "Show");
    await expect(field(page, "E2E shown")).toBeVisible();
    await expect(field(page, "E2E needed").getByText("*")).toBeVisible();
  });

  test("read_only_depends_on locks a field", async ({ pageAs }) => {
    const page = await pageAs("agent");
    await page.goto(NEW_WITH_TEMPLATE);
    await pick(page, "E2E trigger", "Show");
    await expect(field(page, "E2E locked").getByRole("textbox")).toBeDisabled();
  });
});

test.describe("scoping", () => {
  test("portal flag keeps agent and portal scripts apart", async ({ pageAs, ticket }) => {
    await scripts.create(countingAction("E2E Agent Only"));
    await scripts.create(countingAction("E2E Portal Only"), { portal: true });

    const agent = await pageAs("agent");
    await agent.goto(`/opendesk/tickets/${ticket.name}`);
    await expect(agent.getByRole("button", { name: "E2E Agent Only" })).toBeVisible();
    await expect(agent.getByRole("button", { name: "E2E Portal Only" })).toHaveCount(0);

    const customer = await pageAs("customer");
    await customer.goto(`/help/my-tickets/${ticket.name}`);
    await expect(customer.getByRole("button", { name: "E2E Portal Only" })).toBeVisible();
    await expect(customer.getByRole("button", { name: "E2E Agent Only" })).toHaveCount(0);
  });

  test("a disabled script never runs and two enabled scripts combine", async ({
    pageAs,
    ticket,
  }) => {
    await scripts.create(countingAction("E2E First"));
    await scripts.create(countingAction("E2E Second"));
    await scripts.create(countingAction("E2E Disabled"), { enabled: false });

    const page = await pageAs("agent");
    await page.goto(`/opendesk/tickets/${ticket.name}`);
    await expect(page.getByRole("button", { name: "E2E First" })).toBeVisible();
    await expect(page.getByRole("button", { name: "E2E Second" })).toBeVisible();
    await expect(page.getByRole("button", { name: "E2E Disabled" })).toHaveCount(0);
  });

  test.fixme("new ticket scripts respect the portal flag", async ({ pageAs }) => {
    // od_ticket_template/api.py get_one hardcodes is_customer_portal=False.
    await scripts.create(countingAction("E2E Agent New Only"), { newPage: true });
    await scripts.create(countingAction("E2E Portal New Only"), { newPage: true, portal: true });

    const customer = await pageAs("customer");
    await customer.goto("/help/my-tickets/new");
    await expect(customer.getByRole("button", { name: "E2E Portal New Only" })).toBeVisible();
    await expect(customer.getByRole("button", { name: "E2E Agent New Only" })).toHaveCount(0);
  });
});

function field(page: Page, label: string) {
  return page.locator(".space-y-1\\.5").filter({
    has: page.getByText(new RegExp(`^${label}\\b`)),
  });
}

async function pick(page: Page, label: string, option: string) {
  await field(page, label).getByRole("combobox").click();
  await page.getByRole("option", { name: option, exact: true }).click();
}

async function openMoreMenu(page: Page) {
  await page.getByRole("banner").getByRole("button", { name: "More actions" }).click();
}
bytes
9,204
lines
219
Type
ts
Latest commit
28 October 2026