# 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: 3489/dev/manual-mid-card-routing.spec.ts >> [3489][dev] [QA] 수기 MID 카드 제한 라우팅 정책 검증 >> 한결데카유 계정으로 비스킷페이에 로그인한 뒤 수기결제 화면에서 카유 UI가 노출되는지 확인
- Location: src/qa/scenarios/manualMidCardRouting.ts:351:5

# Error details

```
Error: BLOCKED: TA 부서정책 목록 조회 요청이 실패했습니다. status=403 message=잦은 로그인 실패로 접속이 차단되었습니다. 운영사에게 문의 바랍니다.
```

# Test source

```ts
  2071 |
  2072 | function safeFilename(value: string): string {
  2073 |   return value
  2074 |     .replace(/[^a-zA-Z0-9가-힣_.-]+/g, "-")
  2075 |     .replace(/^-+|-+$/g, "")
  2076 |     .slice(0, 90);
  2077 | }
  2078 |
  2079 | function snippet(value: string, maxLength = 1000): string {
  2080 |   const normalized = value.replace(/\s+/g, " ").trim();
  2081 |   return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;
  2082 | }
  2083 |
  2084 | function redactId(value: string): string {
  2085 |   return value ? `${value.slice(0, 8)}...${value.slice(-4)}` : "-";
  2086 | }
  2087 |
  2088 | function redactUrl(value: string): string {
  2089 |   if (!value || value === "-") {
  2090 |     return "-";
  2091 |   }
  2092 |   try {
  2093 |     const url = new URL(value);
  2094 |     url.search = url.search ? "?<redacted>" : "";
  2095 |     return url.toString();
  2096 |   } catch {
  2097 |     return value.replace(/([?&](?:token|key|authorization|access_token)=)[^&]+/gi, "$1redacted");
  2098 |   }
  2099 | }
  2100 |
  2101 | function redactSensitiveJson(value: string): string {
  2102 |   return value
  2103 |     .replace(/("cardNumber"\s*:\s*")[^"]+/gi, "$1redacted")
  2104 |     .replace(/("cardNo"\s*:\s*")[^"]+/gi, "$1redacted")
  2105 |     .replace(/("password"\s*:\s*")[^"]+/gi, "$1redacted")
  2106 |     .replace(/("birth"\s*:\s*")[^"]+/gi, "$1redacted")
  2107 |     .replace(/("phone"\s*:\s*")[^"]+/gi, "$1redacted");
  2108 | }
  2109 |
  2110 | function pruneUndefined(value: Record<string, unknown>): Record<string, unknown> {
  2111 |   return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
  2112 | }
  2113 |
  2114 | function uniqueValues(values: string[]): string[] {
  2115 |   const unique: string[] = [];
  2116 |   for (const value of values) {
  2117 |     if (value && !unique.includes(value)) {
  2118 |       unique.push(value);
  2119 |     }
  2120 |   }
  2121 |   return unique;
  2122 | }
  2123 |
  2124 | function readFirstEnv(keys: string[]): string | undefined {
  2125 |   for (const key of keys) {
  2126 |     if (key && process.env[key]) {
  2127 |       return process.env[key];
  2128 |     }
  2129 |   }
  2130 |   return undefined;
  2131 | }
  2132 |
  2133 | function numberFromEnv(value: string | undefined, fallback: number): number {
  2134 |   const parsed = Number(value);
  2135 |   return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
  2136 | }
  2137 |
  2138 | function resolveApiBaseUrl(runtimeEnv: RuntimeQaEnv): string {
  2139 |   const envKey = runtimeEnv.environment.toUpperCase();
  2140 |   const configured =
  2141 |     process.env[`BIX_API_${envKey}_BASE_URL`] ??
  2142 |     process.env[`BIX_${envKey}_API_BASE_URL`] ??
  2143 |     process.env.BIX_API_BASE_URL;
  2144 |   if (configured) {
  2145 |     return ensureTrailingSlash(configured);
  2146 |   }
  2147 |   if (!runtimeEnv.baseUrl) {
  2148 |     blocked("API baseUrl을 계산할 admin baseUrl 설정이 없습니다.");
  2149 |   }
  2150 |
  2151 |   const baseUrl = new URL(runtimeEnv.baseUrl);
  2152 |   baseUrl.hostname = baseUrl.hostname
  2153 |     .replace(/^admin-dev\./, "api-dev.")
  2154 |     .replace(/^admin-stg\./, "api-stg.")
  2155 |     .replace(/^admin-stage\./, "api-stg.")
  2156 |     .replace(/^store-dev\./, "api-dev.")
  2157 |     .replace(/^store-stg\./, "api-stg.")
  2158 |     .replace(/^store-stage\./, "api-stg.");
  2159 |   return ensureTrailingSlash(baseUrl.origin);
  2160 | }
  2161 |
  2162 | function ensureTrailingSlash(rawUrl: string): string {
  2163 |   return rawUrl.endsWith("/") ? rawUrl : `${rawUrl}/`;
  2164 | }
  2165 |
  2166 | function formatError(error: unknown): string {
  2167 |   return error instanceof Error ? error.message : String(error);
  2168 | }
  2169 |
  2170 | function blocked(message: string): never {
> 2171 |   throw new Error(`BLOCKED: ${message}`);
       |         ^ Error: BLOCKED: TA 부서정책 목록 조회 요청이 실패했습니다. status=403 message=잦은 로그인 실패로 접속이 차단되었습니다. 운영사에게 문의 바랍니다.
  2172 | }
  2173 |
```