# 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: 3485/dev/company-profit-aggregate.spec.ts >> [3485][dev] [QA] 업체별 수익집계 마감/수수료 계산 검증 >> 테넌트관리 > 도메인설정에서 업체 생성 시 이체 수수료 기준 단가를 입력/수정할 수 있는지 확인
- Location: src/qa/scenarios/companyProfitAggregate.ts:146:5

# Error details

```
Error: 업체 생성 폼에서 이체수수료 기준 단가가 보여야 합니다.

expect(received).toContain(expected) // indexOf

Expected substring: "이체수수료 기준 단가"
Received string:    "테스트 업체명 업체명 1차 도메인 1차 도메인 스토어 도메인 스토어 도메인 초기화 검색 테이블 프리셋 설정 프리셋 1 프리셋 2 프리셋 3 필터 생성 # 작업 업체명 파비콘 로고 1차 도메인 스토어 도메인 사이트코드 대금지급 2차인증 통보주소 1 빅스페이먼츠 biscuitpay.com store-dev.biscuitpay.com secret-redacted56 - X https://api.biscuitpay.com/api/v1/payment/notification/bteca 2 minecraft 2dev.biscuitpay.com store.biscuitpay.com1 111111 - X https://api.2dev.biscuitpay.com/api/v1/payment/notification/bteca 3 pg연동테스트 asdtasdt asdtasdtasdt - - X https://api.asdtasdt/api/v1/payment/notification/bteca 4 고라파덕 biscuitpay.com1 store.biscuitpay.com1 000000 - X https://api.biscuitpay.com1/api/v1/payment/notification/bteca 5 두바이페이 admin-dubai.dubai.com store-dubai.dubai.com 333333 - X https://api.admin-dubai.dubai.com/api/v1/payment/notification/bteca 6 렌탈페이테스트 www www 222222 - X https://api.www/api/v1/payment/notification/bteca 7 스빅페이먼츠 amin-dev.test.com store-dev.test.com - - X https://api.amin-dev.test.com/api/v1/payment/notification/bteca 8 신규1 teststst teststststst - - X https://api.teststst/api/v1/payment/notification/bteca 9 신규2 testsetsetse setsaetsetaset - - X https://api.testsetsetse/api/v1/payment/notification/bteca 10 신규3 1231 123 - - X https://api.1231/api/v1/payment/notification/bteca 11 신규4 - - X - 12 업체테스트1 123123 123123 - - X https://api.123123/api/v1/payment/notification/bteca 13 업체테스트2 test test - - X https://api.test/api/v1/payment/notification/bteca 14 자동추가테스트 123 - - X https://api.123/api/v1/payment/notification/bteca 15 테스트 https://naver.com https://naver.com 122312 - X https://api.https://naver.com/api/v1/payment/notification/bteca 총 15 / 15건 빅스페이먼츠 홈 도메인 설정 167:59:37 연장 secret-redacted 시스템메뉴 이동 범례 법인 영업점 홈 거래내역(크로스페이) 결제내역 보류내역 차감내역 단말기미등록 결제내역 가맹점 정산차감 내역 거래관리 거래내역 상계처리내역 수기결제내역 거래로그 PG거래대사 계좌관리 법인계좌관리 계좌조회 지급계좌관리 입금확인 이체내역 이체하기 이체하기(렌탈페이) 구 이체내역 확인(렌탈페이) 지갑관리 지갑관리 지갑입출금내역 정산관리 PG정산내역 정산내역 통신비관리 채권관리 수납관리 지갑 입출금 내역 계좌 관리 지급계좌 입출금 내역 법인계좌 입출금 내역 영업라인 관리 매출집계 매출집계 정산통계 가맹점매출집계 영업점매출집계 일마감집계 월마감집계 업체관리 법인관리 법인관리 부서별 운영상태관리 계약관리 영업점관리 영업점관리 영업라인조회 영업라인일괄적용 가맹점관리 가맹점관리 TID/MID관리 계정관리 계정관리 사용자프리셋 역할별사용가능 권한관리 사용자 보호 정책 사용자 부서 설정 관리 영업법인 영업대행사관리 영업대행사 계정과목 영업법인영업라인 영업대행사월별정산 영업대행사 이체내역 영업대행사 정산내역 계약법인 자금흐름 법인 법인관리 법인계정과목 영업자 선정산 테넌트한도관리 대시보드 법인한도관리 법인선정산수수료통계 선정산 한도관리 자금조회 정산 통계 비스킷관리 연결계좌관리 비스킷카드관리 법인별 수수료 관리 카드통계 비스킷카드사용관리 비스킷카드신청관리 고객센터 고객센터 문서보관함 공지사항 공지사항 패치노트 공지사항관리 개발자 전용 수기결제등록 거래내역 동기화 PG연동(리뉴얼) 오류내역 동기화 키움페이카드 RDB Backfill 키움페이카드 몽고 Backfill 테넌트관리 도메인설정 알리고 문자발송연동 다날본인인증 연동 카드 BIN 관리 레드마인티켓 레드마인티켓 리스크 관리 블랙리스트 관리 차단계좌 이체시도 내역 IP 차단관리 RM 사업자조회 이상거래 알림 채팅방관리 이상거래 알림 발송내역 이상거래 알림 챗봇 관리 리스크 거래내역 실험실 비스킷카드 대시보드 자금이동내역 법인계좌관리V2 비스킷카드통계 총판 매출집계 비스킷 카드관리 업체별 수익집계 업체명 업체명 1차 도메인 1차 도메인 초기화 검색 상세검색 테이블 프리셋 설정 프리셋 1 프리셋 2 프리셋 3 필터 생성 # 작업 업체명 파비콘 로고 1차 도메인 스토어 도메인 사이트코드 대금지급 2차인증 통보주소 1 빅스페이먼츠 biscuitpay.com store-dev.biscuitpay.com secret-redacted56 - X https://api.biscuitpay.com/api/v1/payment/notification/bteca 2 minecraft 2dev.biscuitpay.com store.biscuitpay.com1 111111 - X https://api.2dev.biscuitpay.com/api/v1/payment/notification/bteca 3 pg연동테스트 asdtasdt asdtasdtasdt - - X https://api.asdtasdt/api/v1/payment/notification/bteca 4 고라파덕 biscuitpay.com1 store.biscuitpay.com1 000000 - X https://api.biscuitpay.com1/api/v1/payment/notification/bteca 5 두바이페이 admin-dubai.dubai.com store-dubai.dubai.com 333333 - X https://api.admin-dubai.dubai.com/api/v1/payment/notification/bteca 6 렌탈페이테스트 www www 222222 - X https://api.www/api/v1/payment/notification/bteca 7 스빅페이먼츠 amin-dev.test.com store-dev.test.com - - X https://api.amin-dev.test.com/api/v1/payment/notification/bteca 8 신규1 teststst teststststst - - X https://api.teststst/api/v1/payment/notification/bteca 9 신규2 testsetsetse setsaetsetaset - - X https://api.testsetsetse/api/v1/payment/notification/bteca 10 신규3 1231 123 - - X https://api.1231/api/v1/payment/notification/bteca 11 신규4 - - X - 12 업체테스트1 123123 123123 - - X https://api.123123/api/v1/payment/notification/bteca 13 업체테스트2 test test - - X https://api.test/api/v1/payment/notification/bteca 14 자동추가테스트 123 - - X https://api.123/api/v1/payment/notification/bteca 15 테스트 https://naver.com https://naver.com 122312 - X https://api.https://naver.com/api/v1/payment/notification/bteca 총 15 / 15건 업체 생성 ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ 취소 생성"
```

# Test source

```ts
  53  |   tenantName?: string;
  54  |   dailyItems?: ProfitDailyItem[];
  55  | }
  56  |
  57  | interface ProfitDailyItem {
  58  |   closureDate?: string;
  59  |   salesAmount?: number;
  60  |   transferFeeAmount?: number;
  61  |   completedTransferCount?: number;
  62  |   transferFeeBaseAmount?: number;
  63  |   transferFeeAdditionalChargeAmount?: number;
  64  |   accountInquiryCount?: number;
  65  |   platformFeeAmount?: number;
  66  | }
  67  |
  68  | interface ProfitTotals {
  69  |   salesAmount: number;
  70  |   transferFeeAmount: number;
  71  |   completedTransferCount: number;
  72  |   transferFeeBaseAmount: number;
  73  |   transferFeeAdditionalChargeAmount: number;
  74  |   accountInquiryCount: number;
  75  |   platformFeeAmount: number;
  76  | }
  77  |
  78  | interface ProfitSnapshot {
  79  |   month: number;
  80  |   status: ApiResult<ClosingStatusPayload>;
  81  |   ledger: ApiResult<ProfitReportPayload>;
  82  |   closing: ApiResult<ProfitClosingPayload>;
  83  | }
  84  |
  85  | interface TransferTotal {
  86  |   totalCount: number;
  87  |   completedCount: number;
  88  |   failedCount: number;
  89  |   amount: number;
  90  |   commissionAmount: number;
  91  |   transferAmount: number;
  92  | }
  93  |
  94  | interface TransactionItem {
  95  |   transaction?: {
  96  |     tenantId?: string;
  97  |     amount?: number | string;
  98  |   };
  99  | }
  100 |
  101 | const ROLE = "ADMIN";
  102 | const YEAR = 2026;
  103 | const OPEN_MONTH = 4;
  104 | const CLOSED_MONTH = 5;
  105 | const CURRENT_CLOSED_MONTH = 6;
  106 | const MAY_FROM = "2026-05-01";
  107 | const MAY_TO = "2026-05-31";
  108 | const TARGET_TENANT_NAME = "빅스페이먼츠";
  109 | const DEFAULT_TRANSFER_FEE_UNIT_PRICE = 550;
  110 |
  111 | let createdSubjectName: string | undefined;
  112 |
  113 | export function runCompanyProfitAggregateSuite(options: ScenarioOptions): void {
  114 |   const metadata = loadIssueMetadata(options.issueId);
  115 |   const checklist = loadChecklist(options.issueId, options.environment);
  116 |   const profitPage = findPageDefinition(checklist.pages, "업체별 수익집계");
  117 |   const tenantPage = findPageDefinition(checklist.pages, "도메인설정");
  118 |   const salesAgencySubjectPage = findPageDefinition(checklist.pages, "영업대행사 계정과목");
  119 |   const corporationSubjectPage = findPageDefinition(checklist.pages, "법인계정과목");
  120 |   const runtimeEnv = getRuntimeQaEnv(options.environment, "admin", ROLE);
  121 |   const blockReason = getRuntimeBlockReason(runtimeEnv);
  122 |
  123 |   test.describe(`[${options.issueId}][${options.environment}] ${metadata.subject}`, () => {
  124 |     test.skip(!!blockReason, blockReason);
  125 |
  126 |     test(checklist.checklist[0], async ({ browser }, testInfo) => {
  127 |       test.setTimeout(80_000);
  128 |       await withTracedPage(browser, runtimeEnv, testInfo, tenantPage.name, ROLE, "domain-edit-transfer-fee", async (page) => {
  129 |         await openTargetPage(page, runtimeEnv, tenantPage);
  130 |         await openTenantEditOrDetailForm(page);
  131 |         const bodyText = await visibleText(page);
  132 |
  133 |         expect(bodyText, "업체 수정/상세 폼에서 이체수수료 기준 단가가 보여야 합니다.").toContain("이체수수료 기준 단가");
  134 |         expect(await hasEditableFieldByNearbyText(page, "이체수수료 기준 단가"), "이체수수료 기준 단가 입력란은 수정 가능해야 합니다.").toBe(true);
  135 |
  136 |         await attachEvidenceLog(testInfo, tenantPage.name, ROLE, "domain-edit-transfer-fee", [
  137 |           "테넌트관리 > 도메인설정에서 기존 업체 폼을 열어 이체수수료 기준 단가 항목을 확인했습니다.",
  138 |           "확인 항목: 이체수수료 기준 단가 라벨, 입력 가능 상태",
  139 |           `화면 일부: ${bodyText.slice(0, 1800)}`,
  140 |           "결론: 업체 수정 시 이체 수수료 기준 단가를 수정할 수 있습니다."
  141 |         ]);
  142 |         await attachPageScreenshot(page, testInfo, tenantPage.name, ROLE, "domain-edit-transfer-fee");
  143 |       });
  144 |     });
  145 |
  146 |     test(checklist.checklist[1], async ({ browser }, testInfo) => {
  147 |       test.setTimeout(80_000);
  148 |       await withTracedPage(browser, runtimeEnv, testInfo, tenantPage.name, ROLE, "domain-create-transfer-fee", async (page) => {
  149 |         await openTargetPage(page, runtimeEnv, tenantPage);
  150 |         await openButton(page, /생성|추가/);
  151 |         const bodyText = await visibleText(page);
  152 |
> 153 |         expect(bodyText, "업체 생성 폼에서 이체수수료 기준 단가가 보여야 합니다.").toContain("이체수수료 기준 단가");
      |                                                             ^ Error: 업체 생성 폼에서 이체수수료 기준 단가가 보여야 합니다.
  154 |         expect(await hasEditableFieldByNearbyText(page, "이체수수료 기준 단가"), "이체수수료 기준 단가 입력란은 생성 시 입력 가능해야 합니다.").toBe(true);
  155 |
  156 |         await attachEvidenceLog(testInfo, tenantPage.name, ROLE, "domain-create-transfer-fee", [
  157 |           "도메인설정 생성 폼을 열어 이체수수료 기준 단가 입력 항목을 확인했습니다.",
  158 |           "확인 항목: 생성 폼 내 이체수수료 기준 단가 라벨, 기본값/입력 가능 상태",
  159 |           `화면 일부: ${bodyText.slice(0, 1800)}`,
  160 |           "결론: 업체 생성 시 이체 수수료 기준 단가를 입력/수정할 수 있습니다."
  161 |         ]);
  162 |         await attachPageScreenshot(page, testInfo, tenantPage.name, ROLE, "domain-create-transfer-fee");
  163 |       });
  164 |     });
  165 |
  166 |     test(checklist.checklist[2], async ({ browser }, testInfo) => {
  167 |       test.setTimeout(80_000);
  168 |       await withTracedPage(browser, runtimeEnv, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-option", async (page) => {
  169 |         await openTargetPage(page, runtimeEnv, salesAgencySubjectPage);
  170 |         const listText = await visibleText(page);
  171 |         await openSubjectCreateModal(page);
  172 |         await openPolicyTypeOptions(page);
  173 |         const optionText = await visibleText(page);
  174 |
  175 |         expect(`${listText}\n${optionText}`, "계정과목 정책 타입에 플랫폼수수료가 보여야 합니다.").toContain("플랫폼수수료");
  176 |
  177 |         await attachEvidenceLog(testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-option", [
  178 |           "영업대행사 계정과목 목록/추가 모달에서 정책 타입 옵션을 확인했습니다.",
  179 |           `기존 목록 플랫폼수수료 포함: ${listText.includes("플랫폼수수료") ? "yes" : "no"}`,
  180 |           `옵션 화면 플랫폼수수료 포함: ${optionText.includes("플랫폼수수료") ? "yes" : "no"}`,
  181 |           "참고: 법인계정과목 화면은 현재 정책 타입 컬럼/선택 UI가 없는 기존 구조로 확인되어, 정책 타입 검증은 영업대행사 계정과목 화면 기준으로 수행했습니다.",
  182 |           "결론: 정책 타입에서 플랫폼 수수료 리스트가 표시됩니다."
  183 |         ]);
  184 |         await attachPageScreenshot(page, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-option");
  185 |       });
  186 |     });
  187 |
  188 |     test(checklist.checklist[3], async ({ browser }, testInfo) => {
  189 |       test.setTimeout(60_000);
  190 |       await withTracedPage(browser, runtimeEnv, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-save", async (page) => {
  191 |         createdSubjectName = `QA3485-플랫폼-${Date.now()}`;
  192 |         await createPlatformFeeSubject(page, runtimeEnv, salesAgencySubjectPage, createdSubjectName);
  193 |         const bodyText = await visibleText(page);
  194 |
  195 |         expect(bodyText, "저장 후 목록에 생성한 계정과목명이 보여야 합니다.").toContain(createdSubjectName);
  196 |         expect(bodyText, "저장 후 목록의 정책 타입은 플랫폼수수료로 보여야 합니다.").toContain("플랫폼수수료");
  197 |
  198 |         await attachEvidenceLog(testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-save", [
  199 |           "영업대행사 계정과목에서 정책 타입을 플랫폼수수료로 선택해 저장했습니다.",
  200 |           `생성 계정과목명: ${createdSubjectName}`,
  201 |           `화면 일부: ${bodyText.slice(0, 1800)}`,
  202 |           "결론: 플랫폼 수수료 정책 타입 선택 후 저장됩니다."
  203 |         ]);
  204 |         await attachPageScreenshot(page, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-save");
  205 |       });
  206 |     });
  207 |
  208 |     test(checklist.checklist[4], async ({ browser }, testInfo) => {
  209 |       test.setTimeout(80_000);
  210 |       await withTracedPage(browser, runtimeEnv, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-add", async (page) => {
  211 |         await openTargetPage(page, runtimeEnv, salesAgencySubjectPage);
  212 |         await openSubjectCreateModal(page);
  213 |         await openPolicyTypeOptions(page);
  214 |         const bodyText = await visibleText(page);
  215 |
  216 |         expect(bodyText, "계정과목 추가 모달에서 플랫폼수수료 정책 타입을 선택할 수 있어야 합니다.").toContain("플랫폼수수료");
  217 |         if (createdSubjectName) {
  218 |           await page.keyboard.press("Escape").catch(() => undefined);
  219 |           await openTargetPage(page, runtimeEnv, salesAgencySubjectPage);
  220 |           await expect(page.locator("body"), "이전 저장 계정과목이 목록에 보여야 합니다.").toContainText(createdSubjectName, { timeout: 10_000 });
  221 |         }
  222 |
  223 |         await attachEvidenceLog(testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-add", [
  224 |           "계정과목 추가 모달에서 플랫폼수수료 정책 타입을 선택할 수 있는지 확인했습니다.",
  225 |           createdSubjectName ? `직전 저장 증거 계정과목명: ${createdSubjectName}` : "직전 저장 계정과목명: 없음",
  226 |           "결론: 계정과목 추가 시 플랫폼 수수료 정책 타입으로 추가할 수 있습니다."
  227 |         ]);
  228 |         await attachPageScreenshot(page, testInfo, salesAgencySubjectPage.name, ROLE, "platform-fee-add");
  229 |       });
  230 |     });
  231 |
  232 |     test(checklist.checklist[5], async ({ browser }, testInfo) => {
  233 |       test.setTimeout(70_000);
  234 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "april-open-ledger", async (page) => {
  235 |         await openTargetPage(page, runtimeEnv, profitPage);
  236 |         const snapshot = await loadProfitSnapshot(page, runtimeEnv, OPEN_MONTH);
  237 |         expect(snapshot.status.payload?.closed, "2026년 4월은 마감 전이어야 합니다.").toBe(false);
  238 |         expect(snapshot.ledger.payload?.items?.length ?? 0, "2026년 4월 원장 기준 데이터가 있어야 합니다.").toBeGreaterThan(0);
  239 |         expect(snapshot.closing.status, "2026년 4월은 저장된 마감 데이터가 없어야 합니다.").toBe(400);
  240 |
  241 |         await attachProfitEvidence(testInfo, profitPage.name, ROLE, "april-open-ledger", snapshot, [
  242 |           "2026년 4월 마감 상태와 원장 데이터를 API로 확인했습니다.",
  243 |           "결론: 2026년 4월은 마감 전 상태이며 원장 기준 데이터가 조회됩니다."
  244 |         ]);
  245 |         await attachPageScreenshot(page, testInfo, profitPage.name, ROLE, "april-open-ledger");
  246 |       });
  247 |     });
  248 |
  249 |     test(checklist.checklist[6], async ({ browser }, testInfo) => {
  250 |       test.setTimeout(70_000);
  251 |       await withTracedPage(browser, runtimeEnv, testInfo, profitPage.name, ROLE, "may-closed-snapshot", async (page) => {
  252 |         const snapshot = await loadProfitSnapshotFromOpenedPage(page, runtimeEnv, profitPage, CLOSED_MONTH);
  253 |         expect(snapshot.status.payload?.closed, "2026년 5월은 마감완료여야 합니다.").toBe(true);
```