# 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: 3590/dev/settlement-waiting-account-scope.spec.ts >> [3590][dev] [QA] 정산 대기 처리 지급계좌 후보 범위 및 이력 영역 권한 노출 >> ta 계정에서 정산 대기 지급계좌 후보에 테넌트 지급계좌와 법인 지급계좌가 함께 보이는지 확인
- Location: src/qa/scenarios/settlementWaitingAccountScope.ts:117:5

# Error details

```
Error: TA 지급계좌 후보에 테넌트 지급계좌(isMonify=true)가 포함되어야 합니다.

expect(received).toBeGreaterThan(expected)

Expected: > 0
Received:   0
```

# Test source

```ts
  22  |
  23  | interface DisbursementAccountPayload {
  24  |   disbursementAccounts?: DisbursementAccountCandidate[];
  25  |   corporationAccount?: {
  26  |     id?: number;
  27  |     name?: string;
  28  |     balance?: number;
  29  |   };
  30  |   tenantLimit?: {
  31  |     limit?: number;
  32  |     used?: number;
  33  |   };
  34  |   transferAmount?: number;
  35  | }
  36  |
  37  | interface RoleInspectionSnapshot {
  38  |   role: string;
  39  |   currentUrl: string;
  40  |   pageText: string;
  41  |   waitingRows: string[];
  42  |   waitingRowButtons: string[];
  43  |   modalOpened: boolean;
  44  |   modalText: string;
  45  |   visibleControlTexts: string[];
  46  |   visibleAccountControlNames: string[];
  47  |   selectedAccountName?: string;
  48  |   executeButtonVisible: boolean;
  49  |   executeButtonEnabled: boolean;
  50  |   accountPayload?: DisbursementAccountPayload;
  51  |   historyPayload?: unknown;
  52  |   allAccountNames: string[];
  53  |   tenantAccountNames: string[];
  54  |   corporationAccountNames: string[];
  55  |   hasHistoryArea: boolean;
  56  |   hasHistoryTitle: boolean;
  57  |   forbiddenAccountNames: string[];
  58  |   forbiddenVisibleAccountNames: string[];
  59  | }
  60  |
  61  | const ROLES = ["ADMIN", "TA"] as const;
  62  | const SETTLEMENT_WAITING_BUTTON_TEXT = /^정산대기$/;
  63  | const HISTORY_AREA_PATTERN = /이체\s*실행\s*이력|거래후잔액|발신처|수신처/;
  64  | const HISTORY_TITLE_PATTERN = /(이체\s*실행\s*)?이력/;
  65  | const SETTLEMENT_PROCESS_GUARD = "QA_3590_ALLOW_SETTLEMENT_PROCESS";
  66  |
  67  | test.use({ trace: "on" });
  68  |
  69  | export function runSettlementWaitingAccountScopeSuite(options: ScenarioOptions): void {
  70  |   const metadata = loadIssueMetadata(options.issueId);
  71  |   const checklist = loadChecklist(options.issueId, options.environment);
  72  |   const settlementPage = findPageDefinition(checklist.pages, "정산내역");
  73  |   const items = checklist.checklist;
  74  |
  75  |   test.describe(`[${options.issueId}][${options.environment}] ${metadata.subject}`, () => {
  76  |     test.setTimeout(Number(process.env.QA_TEST_TIMEOUT_MS ?? 120_000));
  77  |
  78  |     test(items[0], async ({ browser }, testInfo) => {
  79  |       const snapshot = await inspectRole(browser, options, settlementPage, "ADMIN", testInfo, "admin-candidates");
  80  |       await attachRoleInspection(testInfo, settlementPage, snapshot, "admin-candidates");
  81  |
  82  |       expect(snapshot.modalOpened, "ADMIN 정산대기 처리 모달이 열려야 합니다.").toBe(true);
  83  |       expect(snapshot.allAccountNames.length, "ADMIN 지급계좌 후보 API에 운영자 허용 범위 후보가 있어야 합니다.").toBeGreaterThan(0);
  84  |       for (const accountName of snapshot.allAccountNames) {
  85  |         expect(snapshot.modalText, `ADMIN 모달에 지급계좌 후보 '${accountName}'이 보여야 합니다.`).toContain(accountName);
  86  |       }
  87  |     });
  88  |
  89  |     test(items[1], async ({ browser }, testInfo) => {
  90  |       const snapshot = await inspectRole(browser, options, settlementPage, "ADMIN", testInfo, "admin-forbidden-scope");
  91  |       await attachRoleInspection(testInfo, settlementPage, snapshot, "admin-forbidden-scope");
  92  |
  93  |       expect(snapshot.modalOpened, "ADMIN 정산대기 처리 모달이 열려야 합니다.").toBe(true);
  94  |       expect(
  95  |         snapshot.visibleAccountControlNames,
  96  |         "ADMIN 화면의 지급계좌 선택 후보는 API가 내려준 후보 목록 안에만 있어야 합니다."
  97  |       ).toEqual(expect.arrayContaining(snapshot.visibleAccountControlNames.filter((name) => snapshot.allAccountNames.includes(name))));
  98  |       expect(
  99  |         snapshot.visibleAccountControlNames.filter((name) => !snapshot.allAccountNames.includes(name)),
  100 |         "ADMIN 화면에 API 후보 밖의 지급계좌가 노출되면 안 됩니다."
  101 |       ).toEqual([]);
  102 |       expect(
  103 |         snapshot.forbiddenVisibleAccountNames,
  104 |         `ADMIN 화면/API 후보에 허용되지 않은 지급계좌가 보이면 안 됩니다: ${snapshot.forbiddenAccountNames.join(", ")}`
  105 |       ).toEqual([]);
  106 |     });
  107 |
  108 |     test(items[2], async ({ browser }, testInfo) => {
  109 |       const snapshot = await inspectRole(browser, options, settlementPage, "ADMIN", testInfo, "admin-history");
  110 |       await attachRoleInspection(testInfo, settlementPage, snapshot, "admin-history");
  111 |
  112 |       expect(snapshot.modalOpened, "ADMIN 정산대기 처리 모달이 열려야 합니다.").toBe(true);
  113 |       expect(snapshot.hasHistoryArea, "ADMIN 계정에서 지급계좌 이력 영역이 보여야 합니다.").toBe(true);
  114 |       expect(snapshot.hasHistoryTitle, "ADMIN 이력 영역 제목에 '이력' 문구가 보여야 합니다.").toBe(true);
  115 |     });
  116 |
  117 |     test(items[3], async ({ browser }, testInfo) => {
  118 |       const snapshot = await inspectRole(browser, options, settlementPage, "TA", testInfo, "ta-candidates");
  119 |       await attachRoleInspection(testInfo, settlementPage, snapshot, "ta-candidates");
  120 |
  121 |       expect(snapshot.modalOpened, "TA 정산대기 처리 모달이 열려야 합니다.").toBe(true);
> 122 |       expect(snapshot.tenantAccountNames.length, "TA 지급계좌 후보에 테넌트 지급계좌(isMonify=true)가 포함되어야 합니다.").toBeGreaterThan(0);
      |                                                                                                     ^ Error: TA 지급계좌 후보에 테넌트 지급계좌(isMonify=true)가 포함되어야 합니다.
  123 |       expect(snapshot.corporationAccountNames.length, "TA 지급계좌 후보에 법인 지급계좌(isMonify=false)가 포함되어야 합니다.").toBeGreaterThan(0);
  124 |       for (const accountName of [...snapshot.tenantAccountNames, ...snapshot.corporationAccountNames]) {
  125 |         expect(snapshot.modalText, `TA 모달에 지급계좌 후보 '${accountName}'이 보여야 합니다.`).toContain(accountName);
  126 |       }
  127 |     });
  128 |
  129 |     test(items[4], async ({ browser }, testInfo) => {
  130 |       const snapshot = await inspectRole(browser, options, settlementPage, "TA", testInfo, "ta-history-hidden");
  131 |       await attachRoleInspection(testInfo, settlementPage, snapshot, "ta-history-hidden");
  132 |
  133 |       expect(snapshot.modalOpened, "TA 정산대기 처리 모달이 열려야 합니다.").toBe(true);
  134 |       expect(snapshot.hasHistoryArea, "TA 계정에서는 지급계좌 이력 영역이 보이면 안 됩니다.").toBe(false);
  135 |       expect(snapshot.hasHistoryTitle, "TA 계정에서는 이력 제목이 보이면 안 됩니다.").toBe(false);
  136 |     });
  137 |
  138 |     test(items[5], async ({ browser }, testInfo) => {
  139 |       const snapshots: RoleInspectionSnapshot[] = [];
  140 |       for (const role of ROLES) {
  141 |         const snapshot = await inspectRole(browser, options, settlementPage, role, testInfo, `${role.toLowerCase()}-selectable`);
  142 |         snapshots.push(snapshot);
  143 |       }
  144 |       await attachHistoryMatrix(testInfo, settlementPage, snapshots, "candidate-selectable");
  145 |
  146 |       for (const snapshot of snapshots) {
  147 |         expect(snapshot.modalOpened, `${snapshot.role} 정산대기 처리 모달이 열려야 합니다.`).toBe(true);
  148 |         expect(snapshot.visibleAccountControlNames.length, `${snapshot.role} 화면에 선택 가능한 지급계좌 후보가 보여야 합니다.`).toBeGreaterThan(0);
  149 |         expect(snapshot.selectedAccountName, `${snapshot.role} 화면에 보이는 지급계좌 후보를 선택할 수 있어야 합니다.`).toBeTruthy();
  150 |         expect(snapshot.executeButtonVisible, `${snapshot.role} 지급계좌 선택 후 이체 실행 버튼이 보여야 합니다.`).toBe(true);
  151 |         expect(snapshot.executeButtonEnabled, `${snapshot.role} 지급계좌 선택 후 이체 실행 버튼이 활성화되어야 합니다.`).toBe(true);
  152 |       }
  153 |     });
  154 |   });
  155 | }
  156 |
  157 | async function inspectRole(
  158 |   browser: Browser,
  159 |   options: ScenarioOptions,
  160 |   pageDefinition: QaPageDefinition,
  161 |   role: string,
  162 |   testInfo: TestInfo,
  163 |   slug: string
  164 | ): Promise<RoleInspectionSnapshot> {
  165 |   const runtimeEnv = getRuntimeQaEnv(options.environment, pageDefinition.application ?? "admin", role);
  166 |   const skipReason = getRuntimeBlockReason(runtimeEnv) ?? getRoleSkipReason(role);
  167 |   test.skip(!!skipReason, skipReason);
  168 |
  169 |   return withTracedPage(browser, runtimeEnv, testInfo, pageDefinition.name, role, slug, async (page) => {
  170 |     const apiResponses = captureSettlementModalResponses(page);
  171 |     await openSettlementPage(page, pageDefinition, runtimeEnv);
  172 |     const waitingRows = await collectWaitingRows(page);
  173 |     const waitingRowButtons = await collectWaitingRowButtons(page);
  174 |     const waitingButton = await findSettlementWaitingButton(page);
  175 |
  176 |     if (!waitingButton) {
  177 |       const pageText = await bodyTextOf(page);
  178 |       const snapshot: RoleInspectionSnapshot = {
  179 |         role,
  180 |         currentUrl: page.url(),
  181 |         pageText,
  182 |         waitingRows,
  183 |         waitingRowButtons,
  184 |         modalOpened: false,
  185 |         modalText: "",
  186 |         visibleControlTexts: [],
  187 |         visibleAccountControlNames: [],
  188 |         selectedAccountName: undefined,
  189 |         executeButtonVisible: false,
  190 |         executeButtonEnabled: false,
  191 |         accountPayload: undefined,
  192 |         historyPayload: undefined,
  193 |         allAccountNames: [],
  194 |         tenantAccountNames: [],
  195 |         corporationAccountNames: [],
  196 |         hasHistoryArea: false,
  197 |         hasHistoryTitle: false,
  198 |         forbiddenAccountNames: getForbiddenAccountNames(),
  199 |         forbiddenVisibleAccountNames: []
  200 |       };
  201 |       await attachRoleInspection(testInfo, pageDefinition, snapshot, `${slug}-no-action`);
  202 |       await attachPageScreenshot(page, testInfo, pageDefinition.name, role, `${slug}-no-action`);
  203 |       return snapshot;
  204 |     }
  205 |
  206 |     await waitingButton.scrollIntoViewIfNeeded({ timeout: 3_000 }).catch(() => undefined);
  207 |     await waitingButton.click({ timeout: 5_000 });
  208 |     const modal = await waitForSettlementWaitingModal(page);
  209 |     await page.waitForTimeout(1_500);
  210 |     const payloads = await apiResponses.flush();
  211 |     const modalText = await modalTextOf(modal);
  212 |     const accountPayload = extractAccountPayload(payloads);
  213 |     const historyPayload = extractHistoryPayload(payloads);
  214 |     const accounts = accountPayload?.disbursementAccounts ?? [];
  215 |     const allAccountNames = accounts.map((account) => account.name).filter(isNonEmptyString);
  216 |     const tenantAccountNames = accounts.filter((account) => account.isMonify === true).map((account) => account.name).filter(isNonEmptyString);
  217 |     const corporationAccountNames = accounts.filter((account) => account.isMonify !== true).map((account) => account.name).filter(isNonEmptyString);
  218 |     const visibleControlTexts = await collectVisibleControlTexts(modal);
  219 |     const visibleAccountControlNames = allAccountNames.filter((accountName) =>
  220 |       visibleControlTexts.some((text) => text.includes(accountName))
  221 |     );
  222 |     const selectedAccountName = await selectFirstVisibleAccountCandidate(modal, visibleAccountControlNames);
```