diff --git a/recording/playwright/fixtures/browser-wallet.ts b/recording/playwright/fixtures/browser-wallet.ts index 87b1722..fcd8fc8 100644 --- a/recording/playwright/fixtures/browser-wallet.ts +++ b/recording/playwright/fixtures/browser-wallet.ts @@ -63,6 +63,10 @@ export const SEL = { // Setup (first run, no password set) passwordInput: 'input[type="password"]', setupConfirm: 'button:has-text("Confirm")', + providerPubkeyInput: "#provider-pubkey", + submitKycButton: 'button:has-text("Submit KYC"):not([disabled])', + kycNameInput: "#kyc-name", + kycPasswordInput: "#kyc-password", // Add wallet (after setup) importTab: 'button:has-text("Import")', @@ -402,11 +406,18 @@ export async function createPrivacyChannel( /** * Open channel picker sheet, add a provider, connect (which navigates to - * the in-popup sign-request page → password → Approve). + * the in-popup sign-request page → password → Approve). After connect, if + * the wallet reports the entity is not yet APPROVED, complete the KYC step. */ export async function addAndConnectProvider( page: Page, - opts: { providerUrl: string; providerName: string; password: string }, + opts: { + providerUrl: string; + providerName: string; + providerPubkey: string; + kycEntityName: string; + password: string; + }, ): Promise { await page.bringToFront(); // Open the channel picker sheet (shows PrivateChannelManager). @@ -418,7 +429,7 @@ export async function addAndConnectProvider( await addToggle.waitFor({ timeout: 10_000 }); await clickWithPause(addToggle); - // Fill provider form. + // Fill provider form (url + name + pubkey). await typeSlowly( page.locator(SEL.providerUrlInput).first(), opts.providerUrl, @@ -427,6 +438,10 @@ export async function addAndConnectProvider( page.locator(SEL.providerNameInput).first(), opts.providerName, ); + await typeSlowly( + page.locator(SEL.providerPubkeyInput).first(), + opts.providerPubkey, + ); // Submit add — picks the form button (not the toggle we just used). // Both have text "Add Provider" — prefer the visible submit inside the form card. @@ -450,8 +465,35 @@ export async function addAndConnectProvider( ); await clickWithPause(page.locator(SEL.signRequestApproveButton).first()); - // Success: navigates back to home with provider connected. The action - // buttons enable (Receive visible) and the header dot turns green. + // After Approve the popup navigates back to home with the channel-picker + // sheet closed. The KYC prompt lives inside that sheet, so re-open it, + // then probe for the "Submit KYC" button and complete the form if shown. + await page.locator(SEL.channelPickerTrigger).first().waitFor({ + timeout: 30_000, + }); + await clickWithPause(page.locator(SEL.channelPickerTrigger).first()); + const kycButton = page.locator(SEL.submitKycButton).first(); + if (await kycButton.isVisible({ timeout: 10_000 }).catch(() => false)) { + await clickWithPause(kycButton); + await typeSlowly( + page.locator(SEL.kycNameInput).first(), + opts.kycEntityName, + ); + await typeSlowly( + page.locator(SEL.kycPasswordInput).first(), + opts.password, + ); + // The button label is identical, but only the form's submit is enabled + // when both inputs are filled — pick the enabled one. + await clickWithPause(page.locator(SEL.submitKycButton).last()); + } + // Close the sheet (whether the KYC step ran or not) so home (with + // Receive/Send/Ramp) is in view and not blocked by the sheet overlay. + await page.keyboard.press("Escape"); + await page.waitForTimeout(500); + + // Success: action buttons enable (Receive visible) and the header dot + // turns green. await page.locator(SEL.receiveButton).first().waitFor({ timeout: 60_000 }); await page .locator('header span.bg-green-400, header [class*="bg-green-400"]') diff --git a/recording/playwright/specs/03-private-transfer.spec.ts b/recording/playwright/specs/03-private-transfer.spec.ts index 5ce60a9..b1ae9c4 100644 --- a/recording/playwright/specs/03-private-transfer.spec.ts +++ b/recording/playwright/specs/03-private-transfer.spec.ts @@ -42,6 +42,31 @@ test.describe.configure({ mode: "serial" }); test("03 — private transfer (Bob receive → Alice deposit + send → Alice withdraw)", async () => { const env = loadRunEnv(); const channelId = requireValue(env, "PRIVACY_CHANNEL_ID"); + const councilId = requireValue(env, "CHANNEL_AUTH_ID"); + + // env.PP_PK is the operator pubkey from setup-recording-keys. The PP that + // spec 02 registered with the platform has its own server-derived pubkey, + // which is what /providers/:ppPublicKey/... URLs use. Look it up via the + // council-platform's public listing, matching on the spec-02 label. + const providerLabel = getProviderName(); + const provListRes = await fetch( + `${env.COUNCIL_PLATFORM_URL}/api/v1/public/providers?councilId=${councilId}`, + ); + if (!provListRes.ok) { + throw new Error( + `Failed to list providers: HTTP ${provListRes.status}`, + ); + } + const provListBody = await provListRes.json() as { + data?: { publicKey: string; label: string }[]; + }; + const match = provListBody.data?.find((p) => p.label === providerLabel); + if (!match?.publicKey) { + throw new Error( + `Could not find provider "${providerLabel}" on council ${councilId}`, + ); + } + const providerPubkey = match.publicKey; const bobHandle = await launchWalletContext({ section: "03-bob", @@ -71,7 +96,9 @@ test("03 — private transfer (Bob receive → Alice deposit + send → Alice wi }); await addAndConnectProvider(bobWallet, { providerUrl: env.PROVIDER_PLATFORM_URL, - providerName: getProviderName(), + providerName: providerLabel, + providerPubkey, + kycEntityName: "Bob", password: RECORDING_PASSWORD, }); @@ -108,7 +135,9 @@ test("03 — private transfer (Bob receive → Alice deposit + send → Alice wi }); await addAndConnectProvider(aliceWallet, { providerUrl: env.PROVIDER_PLATFORM_URL, - providerName: getProviderName(), + providerName: providerLabel, + providerPubkey, + kycEntityName: "Alice", password: RECORDING_PASSWORD, });