import { Page, Route, expect, test } from '@playwright/test'; /** * Track Q Q5 / frontend backlog F62: "repeat webhook and double-click create * exactly one order." The webhook-idempotency half is a backend contract * (PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md ยง6.3, provider + providerEventId as * the dedup key) this suite cannot exercise without a live backend. This * test covers the half that IS frontend-testable: a double-click on the * checkout button must not fire two checkout-session requests. */ const FAKE_ITEM = { categoryID: 1, itemID: 5151, name: 'Idempotency Test Item', photos: null, description: '', currency: 'RUB', price: 500, discount: 0, rating: 0, callbacks: null, questions: null, quantity: 1, }; test('double-clicking checkout sends exactly one checkout-session request', async ({ page, context }) => { await page.addInitScript(item => { window.localStorage.setItem('marketplace_cart', JSON.stringify([item])); }, FAKE_ITEM); await context.addCookies([{ name: 'webSessionID', value: 'e2e-fake-session', domain: 'localhost', path: '/' }]); await page.route('**/users/sessions/**', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ sessionId: 'e2e-fake-session', status: 'active', username: 'e2e_user', userId: 1 }), }), ); await page.route('**/api/v2/pricing/fx-quote**', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ quoteId: 'fxq_e2e', base: 'RUB', quote: 'RUB', rate: 1, source: 'e2e', observedAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 300000).toISOString() }), }), ); let checkoutRequestCount = 0; await page.route('**/api/v2/storefront/checkout', async (route: Route) => { checkoutRequestCount += 1; // Deliberately slow, so a real double-click's second event has to land // while the first request is still in flight - the exact race this test // exists to catch. await new Promise(resolve => setTimeout(resolve, 300)); route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ checkoutSessionId: 'chk_e2e_idempotent', lines: [{ offerId: String(FAKE_ITEM.itemID), qty: 1, unitPrice: { amountMinor: 50000, currency: 'RUB' }, lineTotal: { amountMinor: 50000, currency: 'RUB' }, priceSnapshotId: 'snap_e2e' }], subtotal: { amountMinor: 50000, currency: 'RUB' }, discount: { amountMinor: 0, currency: 'RUB' }, delivery: { amountMinor: 0, currency: 'RUB' }, total: { amountMinor: 50000, currency: 'RUB' }, fxQuoteId: 'fxq_e2e', expiresAt: new Date(Date.now() + 300000).toISOString(), }), }); }); await page.route('**/api/v2/storefront/payments/intents', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ qrId: 'qr_e2e', nspkurl: 'https://example.com/pay', qrTTL: 5 }) }), ); await page.goto('/cart'); await page.waitForLoadState('networkidle'); // No