# 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: 3201/dev/biscuit-management-corporation-scope.spec.ts >> [3201][dev] [QA] 비스킷관리 TA 계정별 법인 범위 분리 재검증 >> 비스킷카드 신청관리에서 secret-redacted과 secret-redacted의 신청 내역이 동일하게 노출되지 않고, 두 계정의 신청 내역이 서로 섞이지 않는지 확인
- Location: src/qa/scenarios/biscuitManagementCorporationScope.ts:102:9

# Error details

```
Error: 두 TA 계정의 조회 결과가 동일하게 섞여 보이면 안 됩니다.

expect(received).toEqual(expected) // deep equality

- Expected  - 1
+ Received  + 3

- Array []
+ Array [
+   "비스킷카드 신청관리: 두 TA 계정의 조회 결과가 동일하게 표시되어 계정별 법인 범위 분리 여부를 확인할 수 없습니다.",
+ ]
```

# Test source

```ts
  17  |   url: string;
  18  |   postData: string;
  19  |   resourceType: string;
  20  | }
  21  |
  22  | interface CorporationOption {
  23  |   text: string;
  24  |   value?: string;
  25  |   index: number;
  26  | }
  27  |
  28  | interface CorporationFilterState {
  29  |   control?: Locator;
  30  |   isNativeSelect: boolean;
  31  |   options: CorporationOption[];
  32  |   source: "filter" | "visible-data";
  33  | }
  34  |
  35  | interface ScopeSnapshot {
  36  |   selected: CorporationOption;
  37  |   alternative?: CorporationOption;
  38  |   bodyText: string;
  39  |   rowText: string;
  40  |   requestFingerprint: string;
  41  |   requests: CapturedRequest[];
  42  | }
  43  |
  44  | interface AccountResultSnapshot {
  45  |   bodyText: string;
  46  |   rowText: string;
  47  |   requestFingerprint: string;
  48  |   requests: CapturedRequest[];
  49  | }
  50  |
  51  | interface CorporationScopeExpectation {
  52  |   selectedCorporation?: string;
  53  |   forbiddenCorporation?: string;
  54  | }
  55  |
  56  | interface ScopeAccount {
  57  |   role: string;
  58  |   label: string;
  59  |   artifactRole: string;
  60  |   runtimeEnv: RuntimeQaEnv;
  61  |   expectation: CorporationScopeExpectation;
  62  | }
  63  |
  64  | const targetRole = "TA";
  65  |
  66  | function resolveScopeAccounts(checklist: QaChecklist, environment: QaEnvironment): ScopeAccount[] {
  67  |   const configuredAccounts = (checklist.subjectAccounts ?? [])
  68  |     .filter((account) => account.role.toUpperCase() === targetRole);
  69  |   const accounts = configuredAccounts.length > 0
  70  |     ? configuredAccounts
  71  |     : [{ role: targetRole, label: targetRole, envRole: targetRole }] satisfies QaSubjectAccountDefinition[];
  72  |
  73  |   return accounts.map((account, index) => {
  74  |     const runtimeEnv = getRuntimeQaEnv(environment, "admin", account.envRole ?? account.role);
  75  |     return {
  76  |       role: account.role.toUpperCase(),
  77  |       label: account.label,
  78  |       artifactRole: safeFilename(`${account.role.toUpperCase()}-${index + 1}-${account.label}`),
  79  |       runtimeEnv,
  80  |       expectation: {
  81  |         selectedCorporation: account.selectedCorporation,
  82  |         forbiddenCorporation: account.forbiddenCorporation
  83  |       }
  84  |     };
  85  |   });
  86  | }
  87  |
  88  | interface AggregatedVerificationResult {
  89  |   failures: string[];
  90  |   blockedPages: string[];
  91  |   evidence: string[];
  92  | }
  93  |
  94  | export function runBiscuitManagementCorporationScopeSuite(options: ScenarioOptions): void {
  95  |   const metadata = loadIssueMetadata(options.issueId);
  96  |   const checklist = loadChecklist(options.issueId, options.environment);
  97  |   const pages = checklist.pages;
  98  |   const accounts = resolveScopeAccounts(checklist, options.environment);
  99  |
  100 |   test.describe(`[${options.issueId}][${options.environment}] ${metadata.subject}`, () => {
  101 |       for (const pageDefinition of pages) {
  102 |         test(compareAccountsChecklistItem(checklist.checklist, pageDefinition), async ({ browser }, testInfo) => {
  103 |           testInfo.setTimeout(90_000);
  104 |           const result = await verifyPageResultsSeparatedAcrossAccounts(browser, accounts, pageDefinition, testInfo);
  105 |
  106 |           await attachEvidenceLog(
  107 |             testInfo,
  108 |             pageDefinition,
  109 |             targetRole,
  110 |             "account-result-compare",
  111 |             result.evidence
  112 |           );
  113 |
  114 |           if (result.blockedPages.length > 0) {
  115 |             blocked(`테스트 데이터 부족 또는 비교 조건 부족: ${result.blockedPages.join(" / ")}`);
  116 |           }
> 117 |           expect(result.failures, "두 TA 계정의 조회 결과가 동일하게 섞여 보이면 안 됩니다.").toEqual([]);
      |                                                                         ^ Error: 두 TA 계정의 조회 결과가 동일하게 섞여 보이면 안 됩니다.
  118 |         });
  119 |       }
  120 |   });
  121 | }
  122 |
  123 | function compareAccountsChecklistItem(items: string[], pageDefinition: QaPageDefinition): string {
  124 |   const keywordMap: Array<[RegExp, string]> = [
  125 |     [/연결계좌/, "연결계좌관리"],
  126 |     [/비스킷카드관리/, "비스킷카드관리"],
  127 |     [/신청관리/, "신청관리"],
  128 |     [/수수료/, "수수료"],
  129 |     [/카드통계/, "카드통계"],
  130 |     [/사용관리|사용내역/, "사용"]
  131 |   ];
  132 |   const pageKeyword = keywordMap.find(([pattern]) => pattern.test(pageDefinition.name))?.[1] ?? pageDefinition.name;
  133 |   return items.find((item) => item.includes(pageKeyword) && /동일|섞이지|분리/.test(item)) ??
  134 |     `${pageDefinition.name}에서 두 TA 계정의 조회 결과가 서로 섞이지 않는지 확인`;
  135 | }
  136 |
  137 | async function verifyPageResultsSeparatedAcrossAccounts(
  138 |   browser: Browser,
  139 |   accounts: ScopeAccount[],
  140 |   pageDefinition: QaPageDefinition,
  141 |   testInfo: TestInfo
  142 | ): Promise<AggregatedVerificationResult> {
  143 |   const failures: string[] = [];
  144 |   const blockedPages: string[] = [];
  145 |   const evidence: string[] = [
  146 |     `${pageDefinition.name} 화면을 서로 다른 법인 범위의 TA 계정으로 각각 조회해 결과가 동일하게 섞여 보이지 않는지 비교했습니다.`,
  147 |     "개별 row의 법인 소속은 직접 판정하지 않고, 동일 조건에서 보이는 목록/요청 기준의 분리 여부를 비교했습니다.",
  148 |     "기본 조회에서 데이터가 없으면 초기화/검색을 한 번 더 수행한 뒤 비교했습니다."
  149 |   ];
  150 |   const snapshots: Array<{ account: ScopeAccount; snapshot: AccountResultSnapshot }> = [];
  151 |
  152 |   if (accounts.length < 2) {
  153 |     return {
  154 |       failures,
  155 |       blockedPages: ["서로 다른 TA 계정 2개가 필요합니다."],
  156 |       evidence
  157 |     };
  158 |   }
  159 |
  160 |   for (const account of accounts) {
  161 |     const blockReason = getRuntimeBlockReason(account.runtimeEnv);
  162 |     if (blockReason) {
  163 |       blockedPages.push(`${account.label}: ${blockReason}`);
  164 |       evidence.push(`${account.label}: BLOCKED / ${blockReason}`);
  165 |       continue;
  166 |     }
  167 |
  168 |     await withRolePage(browser, account.runtimeEnv, async (page) => {
  169 |       try {
  170 |         const snapshot = await collectAccountResultSnapshot(page, pageDefinition, account.runtimeEnv);
  171 |         snapshots.push({ account, snapshot });
  172 |         evidence.push(
  173 |           `${account.label}: 목록 행 ${countComparableRows(snapshot.rowText)}건, 조회 요청 ${snapshot.requests.length}건`,
  174 |           formatRequests(`${pageDefinition.name} / ${account.label} 요청`, snapshot.requests, account.runtimeEnv)
  175 |         );
  176 |       } catch (error) {
  177 |         const message = isBlockedError(error) ? blockedMessage(error) : errorMessage(error);
  178 |         blockedPages.push(`${account.label}: ${message}`);
  179 |         evidence.push(`${account.label}: ${isBlockedError(error) ? "BLOCKED" : "FAIL"} / ${message}`);
  180 |       } finally {
  181 |         await attachPageScreenshot(page, testInfo, pageDefinition, account.artifactRole, "account-result-compare");
  182 |       }
  183 |     });
  184 |   }
  185 |
  186 |   if (snapshots.length < 2) {
  187 |     blockedPages.push(`${pageDefinition.name}: 비교 가능한 TA 계정 결과가 2개 미만입니다.`);
  188 |     return { failures, blockedPages, evidence };
  189 |   }
  190 |
  191 |   const [first, second] = snapshots;
  192 |   const firstRowCount = countComparableRows(first.snapshot.rowText);
  193 |   const secondRowCount = countComparableRows(second.snapshot.rowText);
  194 |   if (firstRowCount === 0 && secondRowCount === 0) {
  195 |     blockedPages.push(`${pageDefinition.name}: 두 계정 모두 조회 결과가 없어 접근 범위 분리 여부를 비교할 수 없습니다.`);
  196 |     return { failures, blockedPages, evidence };
  197 |   }
  198 |
  199 |   const firstSignature = accountComparisonSignature(first.snapshot);
  200 |   const secondSignature = accountComparisonSignature(second.snapshot);
  201 |   const separated = firstSignature !== secondSignature;
  202 |
  203 |   evidence.push(
  204 |     `${pageDefinition.name}: ${first.account.label} 행 ${firstRowCount}건 ↔ ${second.account.label} 행 ${secondRowCount}건`,
  205 |     `${pageDefinition.name}: 두 계정 조회 결과 ${separated ? "서로 다름" : "동일함"}`
  206 |   );
  207 |
  208 |   if (!separated) {
  209 |     failures.push(`${pageDefinition.name}: 두 TA 계정의 조회 결과가 동일하게 표시되어 계정별 법인 범위 분리 여부를 확인할 수 없습니다.`);
  210 |   }
  211 |
  212 |   return { failures, blockedPages, evidence };
  213 | }
  214 |
  215 | async function collectAccountResultSnapshot(
  216 |   page: Page,
  217 |   pageDefinition: QaPageDefinition,
```