import type { Browser, Page } from '@playwright/test'; export const DOC = '/g/monnaie-libre/docs/acte-engagement-certification'; export const TEST_DOC = '/g/essai/docs/acte-engagement-certification'; /** The vote account of the e2e server (serve.sh): Dave's development key. */ export const VOTE_ACCOUNT = 'g1L5EYvvSBaYuQCPW78bHxYcvHfmWAqHYe3Gsrp9SV4kWZaEM'; /** Wallets of the simulated chain (chain-mock.mjs): Alice and Charlie are members, Bob is not. */ export const ALICE = { address: 'g1Pn1geRZnHcVdWdKkoYwQrAARDTXK1D7H3zuXj7GNayT1thu', name: 'Alice' }; export const BOB = { address: 'g1NCrQaFReZcbUW8pKsazfWFCKNSdrTomnDraUMybJc4DFF5g', name: 'Bob' }; export const CHARLIE = { address: 'g1NFWUkPTyZWE77YUrfBKfAhV9W81qmgdD17v1NGhb1EjYFqi', name: 'Charlie' }; export const EVE = { address: 'g1QBoGEqZuxL77pF4rMa25f1cH6HAxwgBq66zZu76JPqsaVgj', name: 'Eve' }; export async function signIn(page: Page, name: string, admin = false) { const r = await page.request.post('/api/auth/dev', { data: { name, admin }, headers: { 'x-requested-with': 'libredecision' }, }); if (!r.ok()) throw new Error(`dev login ${r.status()}`); } export async function person(browser: Browser, name: string, admin = false): Promise { const ctx = await browser.newContext({ locale: 'fr-FR' }); const page = await ctx.newPage(); await signIn(page, name, admin); return page; } /** * A fake wallet extension, as duniter-connect offers itself to pages * (`window.injectedWeb3`): it lists `accounts` and signs whatever it is given, * recording it in `window.__ldSigned`; `window.__ldRefuse = true` makes it refuse; * `window.__ldHold = true` makes it wait, as a person who has not answered yet, * until `window.__ldAnswer(true | false)` (sign, or refuse). * It signs nothing real: the page, in test mode, sends to the simulated node. */ export async function wallet(page: Page, accounts: { address: string; name: string }[]) { await page.addInitScript((list) => { const w = window as unknown as Record & { __ldSigned: unknown[]; __ldRefuse?: boolean; __ldHold?: boolean; __ldAnswer?: (yes: boolean) => void; }; w.__ldSigned = []; w.injectedWeb3 = { 'duniter-connect': { version: 'e2e', enable: async () => ({ accounts: { get: async () => list, subscribe: () => () => undefined }, signer: { signPayload: async (payload: Record) => { if (w.__ldHold) { const yes = await new Promise((resolve) => (w.__ldAnswer = resolve)); if (!yes) throw new Error('Cancelled'); } if (w.__ldRefuse) throw new Error('Cancelled'); w.__ldSigned.push(payload); return { id: w.__ldSigned.length, signature: `0x${'01'.repeat(64)}` }; }, }, }), }, }; }, accounts); } /** What the fake wallet signed, read back (the test extrinsic is JSON as hex). */ export async function signed(page: Page): Promise<{ address: string; dest: string; amount: string; remark: string }[]> { return page.evaluate(() => ((window as unknown as { __ldSigned: { method: string }[] }).__ldSigned ?? []).map((p) => { const bytes = (p.method.slice(2).match(/../g) ?? []).map((b) => parseInt(b, 16)); return JSON.parse(new TextDecoder().decode(Uint8Array.from(bytes))); }), ); } /** A screenshot for the visual check, when `LD_SHOTS` names a directory (never in the loop). */ export async function shot(page: Page, name: string) { const dir = process.env.LD_SHOTS; if (dir) await page.screenshot({ path: `${dir}/${name}.png` }); } /** The control door of the simulated chain, found where the page finds its node. */ export async function chain(page: Page, docPath = DOC) { const [, , group] = docPath.split('/'); const g = await (await page.request.get(`/api/groups/${group}`)).json(); const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json(); const node: string = d.vote.chain.testNode; const base = node.replace(/\/rpc$/, ''); return { doc: d as { doc: { id: string }; units: { code: string; inForce: { id: string } | null; proposals: { id: string }[] }[] }, node, control: async (what: 'hide' | 'show' | 'release' | 'state' | 'silent' | 'node?down=1' | 'node?down=0') => (await page.request.post(`${base}/control/${what}`)).json(), }; }