# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: src/manager/tests/e2e/unit/unit-change-status.spec.ts >> changes the status from blocked to vacant - Location: src/manager/tests/e2e/unit/unit-change-status.spec.ts:12:7 # Error details ``` Error: Failed to sync billing catalog via http://billing-api-dev:8080/e2e/storeroom/import: 500 ``` # Test source ```ts 1 | import { request as playwrightRequest } from '@playwright/test'; 2 | import { sleep } from '@/shared/utils/timeout-utils'; 3 | 4 | type BillingMode = 'modern' | 'classic'; 5 | 6 | const BILLING_STRIPE_SYNC_DELAY_MS = 5_000; 7 | 8 | const DEFAULT_BILLING_API_URL = 'https://billing-api-test.kinnovis.com'; 9 | 10 | // `KINNOVIS_BILLING=1` runs the suite against the modern (Kinnovis) billing system; any other value 11 | // (including `0` and unset) is classic. Mirrors the manager SPA's `isKinnovisBillingUser` switch: in 12 | // modern mode booking writes and invoice mark-as-paid move from the manager API to the billing API. 13 | function resolveBillingMode(): BillingMode { 14 | return process.env.KINNOVIS_BILLING === '1' ? 'modern' : 'classic'; 15 | } 16 | 17 | export function isModernBillingMode(): boolean { 18 | return resolveBillingMode() === 'modern'; 19 | } 20 | 21 | // Calls one of billing's unauthenticated e2e routes; `action` describes the intent in failure 22 | // messages, e.g. `Failed to via : `. 23 | async function callBillingE2eRoute(path: string, action: string): Promise { 24 | const baseURL = process.env.BILLING_API_URL ?? DEFAULT_BILLING_API_URL; 25 | const ctx = await playwrightRequest.newContext({ 26 | baseURL, 27 | extraHTTPHeaders: { Accept: 'application/json' }, 28 | }); 29 | 30 | try { 31 | const response = await ctx.get(path); 32 | 33 | if (!response.ok()) { > 34 | throw new Error(`Failed to ${action} via ${baseURL}${path}: ${response.status()} ${await response.text()}`); | ^ Error: Failed to sync billing catalog via http://billing-api-dev:8080/e2e/storeroom/import: 500 35 | } 36 | } finally { 37 | await ctx.dispose(); 38 | } 39 | } 40 | 41 | // Pins the shared QA storeroom tenant to the resolved billing mode via billing's unauthenticated 42 | // e2e reset route. Used by global setup so runs are deterministic regardless of the mode a 43 | // previous run left the environment in. 44 | export async function pinBillingMode(): Promise { 45 | const mode = resolveBillingMode(); 46 | await callBillingE2eRoute(`/e2e/storeroom/${mode}`, `pin billing mode to "${mode}"`); 47 | } 48 | 49 | // The modern billing system only learns about CLASSIC catalog changes through 50 | // `kin:catalog:import`, which is scheduled every five minutes — far too slow for a test 51 | // that creates a unit type (or discount, product, ...) and books it seconds later. This 52 | // triggers the import synchronously via billing's e2e route; the route serializes 53 | // concurrent callers server-side, so parallel workers can call it freely. 54 | // No-op in classic mode, where the billing catalog is not involved. 55 | export async function syncBillingCatalog(): Promise { 56 | if (!isModernBillingMode()) { 57 | return; 58 | } 59 | 60 | await callBillingE2eRoute('/e2e/storeroom/import', 'sync billing catalog'); 61 | } 62 | 63 | // The catalog import creates a product's price in billing right away but pushes it to Stripe 64 | // asynchronously, so an invoice created straight after fails with Stripe's "No such price". 65 | // Billing exposes no route to await that push, so give it a fixed head start instead. 66 | // No-op in classic mode, where the billing catalog is not involved. 67 | export async function waitForBillingStripeSync(): Promise { 68 | if (!isModernBillingMode()) { 69 | return; 70 | } 71 | 72 | await sleep(BILLING_STRIPE_SYNC_DELAY_MS); 73 | } 74 | ```