# 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:550:5

# Error details

```
Error: /^마감$/ 버튼이 보여야 합니다.

expect(locator).toBeVisible() failed

Locator: getByRole('button', { name: /^마감$/ }).first()
Expected: visible
Timeout: 10000ms
Error: element(s) not found

Call log:
  - /^마감$/ 버튼이 보여야 합니다. with timeout 10000ms
  - waiting for getByRole('button', { name: /^마감$/ }).first()

```

```yaml
- button "공지 배너 닫기"
- text: 테스트
- link "logo 빅스페이먼츠":
  - /url: /
  - img "logo"
  - text: 빅스페이먼츠
- button "사이드바 접기":
  - img
  - img "메뉴 열기"
- navigation "열린 페이지 탭":
  - img
  - text: 홈
  - button "업체별 수익집계" [disabled]:
    - img
    - text: 업체별 수익집계
    - img
  - button "새 탭 열기":
    - img
  - status
- textbox "빅스페이먼츠"
- img
- button "다운로드 이력":
  - img
- img
- text: 167:54:47
- button "연장"
- button "secret-redacted"
- link "시스템메뉴 이동":
  - /url: /admin/account-management/bigs-account
  - text: 시스템메뉴 이동
  - img
- button "범례 범례 설명":
  - text: 범례
  - img
  - img
- text: 법인 영업점
- list:
  - listitem:
    - link "홈":
      - /url: /?tenantId=redacted
      - img
      - text: 홈
  - listitem:
    - img
    - text: 거래내역(크로스페이)
    - button
    - list:
      - button "결제내역 새 탭으로 열기":
        - img
      - link "결제내역":
        - /url: /crosspay/transactions?tenantId=redacted
      - button "보류내역 새 탭으로 열기":
        - img
      - link "보류내역":
        - /url: /crosspay/hold?tenantId=redacted
      - button "차감내역 새 탭으로 열기":
        - img
      - link "차감내역":
        - /url: /crosspay/deduction?tenantId=redacted
      - button "단말기미등록 결제내역 새 탭으로 열기":
        - img
      - link "단말기미등록 결제내역":
        - /url: /crosspay/unregistered-mid?tenantId=redacted
      - button "가맹점 정산차감 내역 새 탭으로 열기":
        - img
      - link "가맹점 정산차감 내역":
        - /url: /crosspay/negative-settlement?tenantId=redacted
  - listitem:
    - img
    - text: 거래관리
    - button
    - list:
      - button "거래내역 새 탭으로 열기":
        - img
      - link "거래내역":
        - /url: /trade-history-v2?tenantId=redacted
      - button "상계처리내역 새 탭으로 열기":
        - img
      - link "상계처리내역":
        - /url: /offset-management?tenantId=redacted
      - button "수기결제내역 새 탭으로 열기":
        - img
      - link "수기결제내역":
        - /url: /log-management/manual-log?tenantId=redacted
      - button "거래로그 새 탭으로 열기":
        - img
      - link "거래로그":
        - /url: /payment-history?tenantId=redacted
      - button "PG거래대사 새 탭으로 열기":
        - img
      - link "PG거래대사":
        - /url: /master-accounting/pg-reconciliation?tenantId=redacted
  - listitem:
    - img
    - text: 계좌관리
    - button
    - list:
      - button "법인계좌관리 새 탭으로 열기":
        - img
      - link "법인계좌관리":
        - /url: /master-accounting/corporation-accounts?tenantId=redacted
      - button "계좌조회 새 탭으로 열기":
        - img
      - link "계좌조회":
        - /url: /account/account-inquiry?tenantId=redacted
      - button "지급계좌관리 새 탭으로 열기":
        - img
      - link "지급계좌관리":
        - /url: /master-accounting/disbursement-accounts?tenantId=redacted
      - button "입금확인 새 탭으로 열기":
        - img
      - link "입금확인":
        - /url: /account/deposit-confirmation?tenantId=redacted
      - button "이체내역 새 탭으로 열기":
        - img
      - link "이체내역":
        - /url: /account/transfer-details?tenantId=redacted
      - button "이체하기 새 탭으로 열기":
        - img
      - link "이체하기":
        - /url: /account/remittance-manage?tenantId=redacted
      - button "이체하기(렌탈페이) 새 탭으로 열기":
        - img
      - link "이체하기(렌탈페이)":
        - /url: /account/remittance-manage-rentalpay?tenantId=redacted
      - button "구 이체내역 확인(렌탈페이) 새 탭으로 열기":
        - img
      - link "구 이체내역 확인(렌탈페이)":
        - /url: /account/legacy-transfer-details?tenantId=redacted
  - listitem:
    - img
    - text: 지갑관리
    - button
    - list:
      - button "지갑관리 새 탭으로 열기":
        - img
      - link "지갑관리":
        - /url: /master-accounting/wallet-management?tenantId=redacted
      - button "지갑입출금내역 새 탭으로 열기":
        - img
      - link "지갑입출금내역":
        - /url: /master-accounting/wallet-inout-history?tenantId=redacted
  - listitem:
    - img
    - text: 정산관리
    - button
    - list:
      - button "PG정산내역 새 탭으로 열기":
        - img
      - link "PG정산내역":
        - /url: /settlement-history/pg-settlement-details?tenantId=redacted
      - button "정산내역 새 탭으로 열기":
        - img
      - link "정산내역":
        - /url: /settlement-history/settlement-details?tenantId=redacted
      - button "통신비관리 새 탭으로 열기":
        - img
      - link "통신비관리":
        - /url: /settlement-history/communication-settlement?tenantId=redacted
      - button "채권관리 새 탭으로 열기":
        - img
      - link "채권관리":
        - /url: /bond-management?tenantId=redacted
      - button "수납관리 새 탭으로 열기":
        - img
      - link "수납관리":
        - /url: /master-accounting/installment-manage?tenantId=redacted
      - button "지갑 입출금 내역 새 탭으로 열기":
        - img
      - link "지갑 입출금 내역":
        - /url: /wallet-history?tenantId=redacted
  - listitem:
    - img
    - text: 계좌 관리
    - button
    - list:
      - button "지급계좌 입출금 내역 새 탭으로 열기":
        - img
      - link "지급계좌 입출금 내역":
        - /url: /master-accounting/disbursement-accounts-inout-history?tenantId=redacted
      - button "법인계좌 입출금 내역 새 탭으로 열기":
        - img
      - link "법인계좌 입출금 내역":
        - /url: /master-accounting/corporation-accounts-inout-history?tenantId=redacted
      - button "영업라인 관리 새 탭으로 열기":
        - img
      - link "영업라인 관리":
        - /url: /master-accounting/salesline-management?tenantId=redacted
  - listitem:
    - img
    - text: 매출집계
    - button
    - list:
      - button "매출집계 새 탭으로 열기":
        - img
      - link "매출집계":
        - /url: /admin/sales-history
      - button "정산통계 새 탭으로 열기":
        - img
      - link "정산통계":
        - /url: /admin/statistics/settlement
      - button "가맹점매출집계 새 탭으로 열기":
        - img
      - link "가맹점매출집계":
        - /url: /transaction-aggregation-details?tenantId=redacted
      - button "영업점매출집계 새 탭으로 열기":
        - img
      - link "영업점매출집계":
        - /url: /branch-profit-aggregate?tenantId=redacted
      - button "일마감집계 새 탭으로 열기":
        - img
      - link "일마감집계":
        - /url: /master-accounting/daily-settlement-closing-overview?tenantId=redacted
      - button "월마감집계 새 탭으로 열기":
        - img
      - link "월마감집계":
        - /url: /master-accounting/monthly-settlement-closing-overview?tenantId=redacted
  - listitem:
    - img
    - text: 업체관리
    - button
    - list:
      - text: 법인관리
      - list:
        - listitem:
          - button "법인관리 새 탭으로 열기":
            - img
          - link "법인관리":
            - /url: /business/corporation?tenantId=redacted
        - listitem:
          - button "부서별 운영상태관리 새 탭으로 열기":
            - img
          - link "부서별 운영상태관리":
            - /url: /saleline-status-control?tenantId=redacted
        - listitem:
          - button "계약관리 새 탭으로 열기":
            - img
          - link "계약관리":
            - /url: /business/pg-contract-manage?tenantId=redacted
      - text: 영업점관리
      - list:
        - listitem:
          - button "영업점관리 새 탭으로 열기":
            - img
          - link "영업점관리":
            - /url: /business/branch?tenantId=redacted
        - listitem:
          - button "영업라인조회 새 탭으로 열기":
            - img
          - link "영업라인조회":
            - /url: /business/salesline-management?tenantId=redacted
        - listitem:
          - button "영업라인일괄적용 새 탭으로 열기":
            - img
          - link "영업라인일괄적용":
            - /url: /business/salesline-preset?tenantId=redacted
      - text: 가맹점관리
      - list:
        - listitem:
          - button "가맹점관리 새 탭으로 열기":
            - img
          - link "가맹점관리":
            - /url: /business/vendor-manage?tenantId=redacted
        - listitem:
          - button "TID/MID관리 새 탭으로 열기":
            - img
          - link "TID/MID관리":
            - /url: /business/pg-mid-manage?tenantId=redacted
      - text: 계정관리
      - list:
        - listitem:
          - button "계정관리 새 탭으로 열기":
            - img
          - link "계정관리":
            - /url: /admin/member
        - listitem:
          - button "사용자프리셋 새 탭으로 열기":
            - img
          - link "사용자프리셋":
            - /url: /business/member-preset?tenantId=redacted
        - listitem:
          - button "역할별사용가능 권한관리 새 탭으로 열기":
            - img
          - link "역할별사용가능 권한관리":
            - /url: /business/permission-available?tenantId=redacted
      - button "사용자 보호 정책 새 탭으로 열기":
        - img
      - link "사용자 보호 정책":
        - /url: /business/user-protection?tenantId=redacted
      - button "사용자 새 탭으로 열기":
        - img
      - link "사용자":
        - /url: /business/member?tenantId=redacted
      - button "부서 설정 관리 새 탭으로 열기":
        - img
      - link "부서 설정 관리":
        - /url: /admin/ta-department-setting
  - listitem:
    - img
    - text: 영업법인
    - button
    - list:
      - button "영업대행사관리 새 탭으로 열기":
        - img
      - link "영업대행사관리":
        - /url: /sales-agency/sales-agency?tenantId=redacted
      - button "영업대행사 계정과목 새 탭으로 열기":
        - img
      - link "영업대행사 계정과목":
        - /url: /sales-agency/account-subject-sales-agency?tenantId=redacted
      - button "영업법인영업라인 새 탭으로 열기":
        - img
      - link "영업법인영업라인":
        - /url: /sales-agency/sales-line?tenantId=redacted
      - button "영업대행사월별정산 새 탭으로 열기":
        - img
      - link "영업대행사월별정산":
        - /url: /admin/statistics/agency-daily
      - button "영업대행사 이체내역 새 탭으로 열기":
        - img
      - link "영업대행사 이체내역":
        - /url: /sales-agency/sales-agency-deposit?tenantId=redacted
      - button "영업대행사 정산내역 새 탭으로 열기":
        - img
      - link "영업대행사 정산내역":
        - /url: /sales-agency/agency-settlement?tenantId=redacted
      - button "계약법인 자금흐름 새 탭으로 열기":
        - img
      - link "계약법인 자금흐름":
        - /url: /sales-agency/agency-fund-hierarchy?tenantId=redacted
  - listitem:
    - img
    - text: 법인
    - button
    - list:
      - button "법인관리 새 탭으로 열기":
        - img
      - link "법인관리":
        - /url: /sales-agency/agency?tenantId=redacted
      - button "법인계정과목 새 탭으로 열기":
        - img
      - link "법인계정과목":
        - /url: /sales-agency/account-subject-agency?tenantId=redacted
      - button "영업자 새 탭으로 열기":
        - img
      - link "영업자":
        - /url: /sales-agency/sales-person?tenantId=redacted
  - listitem:
    - img
    - text: 선정산
    - button
    - list:
      - button "테넌트한도관리 새 탭으로 열기":
        - img
      - link "테넌트한도관리":
        - /url: /admin/tenant-early-settlement-config/limit-manage
      - button "대시보드 새 탭으로 열기":
        - img
      - link "대시보드":
        - /url: /admin/tenant-early-settlement-config/dashboard
      - button "법인한도관리 새 탭으로 열기":
        - img
      - link "법인한도관리":
        - /url: /admin/early-settlement-commission-rate
      - button "법인선정산수수료통계 새 탭으로 열기":
        - img
      - link "법인선정산수수료통계":
        - /url: /master-accounting/early-settlement-commission-statistics?tenantId=redacted
      - button "선정산 한도관리 새 탭으로 열기":
        - img
      - link "선정산 한도관리":
        - /url: /master-accounting/early-settlement-limit?tenantId=redacted
      - button "자금조회 새 탭으로 열기":
        - img
      - link "자금조회":
        - /url: /admin/tenant-early-settlement-config/fund-dashboard
      - button "정산 통계 새 탭으로 열기":
        - img
      - link "정산 통계":
        - /url: /admin/statistics/tenant-early-settlement
  - listitem:
    - img
    - text: 비스킷관리
    - button
    - list:
      - button "연결계좌관리 새 탭으로 열기":
        - img
      - link "연결계좌관리":
        - /url: /admin/biscuit-card/linked-account-setting
      - button "비스킷카드관리 새 탭으로 열기":
        - img
      - link "비스킷카드관리":
        - /url: /admin/biscuit-card/card-manage
      - button "법인별 수수료 관리 새 탭으로 열기":
        - img
      - link "법인별 수수료 관리":
        - /url: /admin/biscuit-card/corporation-commission
      - button "카드통계 새 탭으로 열기":
        - img
      - link "카드통계":
        - /url: /admin/statistics/biscuit-card-corporation
      - button "비스킷카드사용관리 새 탭으로 열기":
        - img
      - link "비스킷카드사용관리":
        - /url: /admin/biscuit-card/usage-history
      - button "비스킷카드신청관리 새 탭으로 열기":
        - img
      - link "비스킷카드신청관리":
        - /url: /admin/biscuit-card/application-manage
  - listitem:
    - img
    - text: 고객센터
    - button
    - list:
      - button "고객센터 새 탭으로 열기":
        - img
      - link "고객센터":
        - /url: /inquiry?tenantId=redacted
      - button "문서보관함 새 탭으로 열기":
        - img
      - link "문서보관함":
        - /url: /document?tenantId=redacted
      - text: 공지사항
      - list:
        - listitem:
          - button "공지사항 새 탭으로 열기":
            - img
          - link "공지사항":
            - /url: /notice?tenantId=redacted
        - listitem:
          - button "패치노트 새 탭으로 열기":
            - img
          - link "패치노트":
            - /url: /patch-note?tenantId=redacted
      - button "공지사항관리 새 탭으로 열기":
        - img
      - link "공지사항관리":
        - /url: /admin/notice-manage
  - listitem:
    - img
    - text: 개발자 전용
    - button
    - list:
      - button "수기결제등록 새 탭으로 열기":
        - img
      - link "수기결제등록":
        - /url: /manual-transaction-entry?tenantId=redacted
      - button "거래내역 동기화 새 탭으로 열기":
        - img
      - link "거래내역 동기화":
        - /url: /sync-transaction?tenantId=redacted
      - button "PG연동(리뉴얼) 새 탭으로 열기":
        - img
      - link "PG연동(리뉴얼)":
        - /url: /admin/pg-provider
      - button "오류내역 동기화 새 탭으로 열기":
        - img
      - link "오류내역 동기화":
        - /url: /backfill-settlement?tenantId=redacted
      - button "키움페이카드 RDB Backfill 새 탭으로 열기":
        - img
      - link "키움페이카드 RDB Backfill":
        - /url: /backfill-card-company?tenantId=redacted
      - button "키움페이카드 몽고 Backfill 새 탭으로 열기":
        - img
      - link "키움페이카드 몽고 Backfill":
        - /url: /backfill-card-company-mongo?tenantId=redacted
  - listitem:
    - img
    - text: 테넌트관리
    - button
    - list:
      - button "도메인설정 새 탭으로 열기":
        - img
      - link "도메인설정":
        - /url: /admin/tenant
      - button "알리고 문자발송연동 새 탭으로 열기":
        - img
      - link "알리고 문자발송연동":
        - /url: /admin/aligo
      - button "다날본인인증 연동 새 탭으로 열기":
        - img
      - link "다날본인인증 연동":
        - /url: /admin/identity-verification
      - button "카드 BIN 관리 새 탭으로 열기":
        - img
      - link "카드 BIN 관리":
        - /url: /admin/card-bin
  - listitem:
    - img
    - text: 레드마인티켓
    - button
    - list:
      - button "레드마인티켓 새 탭으로 열기":
        - img
      - link "레드마인티켓":
        - /url: /redmine?tenantId=redacted
  - listitem:
    - img
    - text: 리스크 관리
    - button
    - list:
      - button "블랙리스트 관리 새 탭으로 열기":
        - img
      - link "블랙리스트 관리":
        - /url: /admin/blacklist-manage
      - button "차단계좌 이체시도 내역 새 탭으로 열기":
        - img
      - link "차단계좌 이체시도 내역":
        - /url: /admin/blacklist-transfer-history
      - button "IP 차단관리 새 탭으로 열기":
        - img
      - link "IP 차단관리":
        - /url: /admin/ip-block-manage
      - button "RM 사업자조회 새 탭으로 열기":
        - img
      - link "RM 사업자조회":
        - /url: /risk-management/rm-business?tenantId=redacted
      - button "이상거래 알림 채팅방관리 새 탭으로 열기":
        - img
      - link "이상거래 알림 채팅방관리":
        - /url: /business/telegram-chat-room-manage?tenantId=redacted
      - button "이상거래 알림 발송내역 새 탭으로 열기":
        - img
      - link "이상거래 알림 발송내역":
        - /url: /business/telegram-history?tenantId=redacted
      - button "이상거래 알림 챗봇 관리 새 탭으로 열기":
        - img
      - link "이상거래 알림 챗봇 관리":
        - /url: /admin/telegram-chatbot-manage
      - button "리스크 거래내역 새 탭으로 열기":
        - img
      - link "리스크 거래내역":
        - /url: /risk-management/risk-transaction?tenantId=redacted
  - listitem:
    - img
    - text: 실험실
    - button
    - list:
      - button "비스킷카드 대시보드 새 탭으로 열기":
        - img
      - link "비스킷카드 대시보드":
        - /url: /admin/biscuit-card/dashboard
      - button "자금이동내역 새 탭으로 열기":
        - img
      - link "자금이동내역":
        - /url: /master-accounting/disbursement-account-fund-flow?tenantId=redacted
      - button "법인계좌관리V2 새 탭으로 열기":
        - img
      - link "법인계좌관리V2":
        - /url: /lab/corporation-accounts-v2?tenantId=redacted
      - button "비스킷카드통계 새 탭으로 열기":
        - img
      - link "비스킷카드통계":
        - /url: /admin/statistics/biscuit-card
      - button "총판 매출집계 새 탭으로 열기":
        - img
      - link "총판 매출집계":
        - /url: /admin/statistics/corporation-profit
      - button "비스킷 카드관리 새 탭으로 열기":
        - img
      - link "비스킷 카드관리":
        - /url: /admin/biscuit-card/card-manage-global
      - button "업체별 수익집계 새 탭으로 열기":
        - img
      - text: 업체별 수익집계
- text: 조회 월
- textbox: 2026-06
- img "calendar"
- text: 업체명
- textbox "업체명":
  - /placeholder: 업체명 입력
- checkbox
- text: 완전 일치 완전 일치
- button "초기화"
- button "검색"
- text: 재마감 원장 확인 현재 원장 기준 집계를 확인 중입니다. 재마감 버튼을 다시 누르면 이 값으로 마감 데이터가 갱신됩니다. 이체수수료 단가
- spinbutton "이체수수료 단가 원": "220"
- text: "원 이체수수료(낼금액) = 완료 이체건수 x 이체수수료 단가 현재 완료 이체건수: 3건 총 계산금액 660 원 총 받을금액 - 낼금액 990 원 예금주조회 단가"
- spinbutton "예금주조회 단가 원": "60"
- text: "원 예금주조회(낼금액) = 예금주조회 전체 건수 x 예금주조회 단가 현재 예금주조회 총 건수: 0건 총 계산금액 0 원"
- button "재마감"
- text: 플랫폼 수수료율 미입력 시 조회 금액 유지
- table:
  - rowgroup:
    - row "업체 매출금 수수료율(%) 계산금액":
      - columnheader "업체"
      - columnheader "매출금"
      - columnheader "수수료율(%)"
      - columnheader "계산금액"
  - rowgroup:
    - row "고라파덕 4,000 원 2 원":
      - cell "고라파덕"
      - cell "4,000 원"
      - cell:
        - textbox "-"
      - cell "2 원"
    - row "빅스페이먼츠 177,014 원 802 원":
      - cell "빅스페이먼츠"
      - cell "177,014 원"
      - cell:
        - textbox "-"
      - cell "802 원"
- text: "매출총액 : 181,014 원 이체수수료(총 받을금액) : 1,650 원 실제 이체수수료 : 0 원 이체수수료 차액 : 1,650 원 플랫폼수수료 : 804 원 완료 이체건수 : 3 건 예금주조회 총 건수 : 0 건"
- table:
  - rowgroup:
    - row "업체 구분 합계 2026-06-01 2026-06-02 2026-06-03 2026-06-04 2026-06-05 2026-06-06 2026-06-07 2026-06-08 2026-06-09 2026-06-10 2026-06-11 2026-06-12 2026-06-13 2026-06-14 2026-06-15 2026-06-16 2026-06-17 2026-06-18 2026-06-19 2026-06-20 2026-06-21 2026-06-22 2026-06-23 2026-06-24 2026-06-25 2026-06-26 2026-06-27 2026-06-28 2026-06-29 2026-06-30":
      - columnheader "업체"
      - columnheader "구분"
      - columnheader "합계"
      - columnheader "2026-06-01"
      - columnheader "2026-06-02"
      - columnheader "2026-06-03"
      - columnheader "2026-06-04"
      - columnheader "2026-06-05"
      - columnheader "2026-06-06"
      - columnheader "2026-06-07"
      - columnheader "2026-06-08"
      - columnheader "2026-06-09"
      - columnheader "2026-06-10"
      - columnheader "2026-06-11"
      - columnheader "2026-06-12"
      - columnheader "2026-06-13"
      - columnheader "2026-06-14"
      - columnheader "2026-06-15"
      - columnheader "2026-06-16"
      - columnheader "2026-06-17"
      - columnheader "2026-06-18"
      - columnheader "2026-06-19"
      - columnheader "2026-06-20"
      - columnheader "2026-06-21"
      - columnheader "2026-06-22"
      - columnheader "2026-06-23"
      - columnheader "2026-06-24"
      - columnheader "2026-06-25"
      - columnheader "2026-06-26"
      - columnheader "2026-06-27"
      - columnheader "2026-06-28"
      - columnheader "2026-06-29"
      - columnheader "2026-06-30"
- table:
  - rowgroup:
    - row "전체 매출금 합계 181,014원 3,004원 34,011원 0원 0원 0원 0원 0원 144,000원 -1원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "전체"
      - cell "매출금 합계"
      - cell "181,014원"
      - cell "3,004원"
      - cell "34,011원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "144,000원"
      - cell "-1원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
    - row "이체수수료(총 받을금액) 합계 1,650원 0원 0원 0원 1,650원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "이체수수료(총 받을금액) 합계"
      - cell "1,650원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "1,650원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
    - row "플랫폼수수료 합계 804원 51원 321원 0원 0원 0원 0원 0원 432원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "플랫폼수수료 합계"
      - cell "804원"
      - cell "51원"
      - cell "321원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "432원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
- table:
  - rowgroup:
    - row "고라파덕 상세 보기 원장에 반영된 결제 금액 합계 (매출 일집계 기준) 4,000원 2,000원 2,000원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "고라파덕 상세 보기":
        - text: 고라파덕
        - button "상세 보기"
      - cell "원장에 반영된 결제 금액 합계 (매출 일집계 기준)": 매출금
      - cell "4,000원"
      - cell "2,000원"
      - cell "2,000원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
    - row "완료 이체건수 * 업체별 이체수수료 단가 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "완료 이체건수 * 업체별 이체수수료 단가": 이체수수료(총 받을금액)
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
    - row "원장에서 플랫폼 수수료로 분류된 금액 합계 2원 1원 1원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원 0원":
      - cell "원장에서 플랫폼 수수료로 분류된 금액 합계": 플랫폼수수료
      - cell "2원"
      - cell "1원"
      - cell "1원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
      - cell "0원"
```

# Test source

```ts
  986  |   role: string,
  987  |   suffix: string,
  988  |   callback: (page: Page) => Promise<void>
  989  | ): Promise<void> {
  990  |   const blockReason = getRuntimeBlockReason(runtimeEnv);
  991  |   if (blockReason) {
  992  |     test.skip(true, blockReason);
  993  |   }
  994  |   if (isRoleSkipped(runtimeEnv.role)) {
  995  |     test.skip(true, `${runtimeEnv.role} 로그인 갱신 실패로 QA를 건너뜁니다.`);
  996  |   }
  997  |
  998  |   const context = await browser.newContext(
  999  |     runtimeEnv.storageState ? { storageState: runtimeEnv.storageState, acceptDownloads: true } : { acceptDownloads: true }
  1000 |   );
  1001 |   const tracePath = testInfo.outputPath(`${safeFilename(pageName)}-${safeFilename(role)}-${safeFilename(suffix)}-trace.zip`);
  1002 |   await context.tracing.start({ screenshots: true, snapshots: true, sources: false });
  1003 |   const page = await context.newPage();
  1004 |
  1005 |   try {
  1006 |     await callback(page);
  1007 |     await stopAndAttachTrace(context, tracePath, { testInfo, pageName, role, suffix });
  1008 |   } catch (error) {
  1009 |     await stopAndAttachTrace(context, tracePath, { testInfo, pageName, role, suffix });
  1010 |     throw error;
  1011 |   } finally {
  1012 |     await context.close().catch(() => undefined);
  1013 |   }
  1014 | }
  1015 |
  1016 | async function openTargetPage(page: Page, runtimeEnv: RuntimeQaEnv, pageDefinition: QaPageDefinition): Promise<void> {
  1017 |   if (!runtimeEnv.baseUrl) {
  1018 |     throw new Error("baseUrl이 필요합니다.");
  1019 |   }
  1020 |   if (!runtimeEnv.tenantId) {
  1021 |     throw new Error("tenantId가 필요합니다.");
  1022 |   }
  1023 |   const targetUrl = buildPageUrl(runtimeEnv.baseUrl, pageDefinition, runtimeEnv.tenantId);
  1024 |   await openAuthenticatedUrl(page, runtimeEnv, new URL(targetUrl).pathname + new URL(targetUrl).search);
  1025 | }
  1026 |
  1027 | async function openAuthenticatedUrl(page: Page, runtimeEnv: RuntimeQaEnv, pathValue: string): Promise<void> {
  1028 |   if (!runtimeEnv.baseUrl) {
  1029 |     throw new Error("baseUrl이 필요합니다.");
  1030 |   }
  1031 |   const targetUrl = new URL(pathValue, ensureTrailingSlash(runtimeEnv.baseUrl)).toString();
  1032 |
  1033 |   if (!runtimeEnv.storageState) {
  1034 |     await loginWithCredentials(page, runtimeEnv);
  1035 |   }
  1036 |
  1037 |   await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
  1038 |   await waitForNavigationToSettle(page);
  1039 |
  1040 |   if (await isLoginPage(page, runtimeEnv)) {
  1041 |     await loginWithCredentials(page, runtimeEnv);
  1042 |     await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
  1043 |     await waitForNavigationToSettle(page);
  1044 |   }
  1045 |
  1046 |   expect(await isLoginPage(page, runtimeEnv), `${runtimeEnv.application} 업무 화면 진입 전 로그인 화면을 벗어나야 합니다.`).toBe(false);
  1047 | }
  1048 |
  1049 | async function waitForNavigationToSettle(page: Page): Promise<void> {
  1050 |   await page.waitForLoadState("domcontentloaded");
  1051 |   await page.waitForLoadState("networkidle", { timeout: 8_000 }).catch(() => undefined);
  1052 |   await page.waitForTimeout(700);
  1053 | }
  1054 |
  1055 | async function openButton(page: Page, name: RegExp): Promise<void> {
  1056 |   const clicked = await page.evaluate(({ source, flags }) => {
  1057 |     const matcher = new RegExp(source, flags);
  1058 |     const roots = [
  1059 |       document.querySelector("[data-route-keep-alive-active='true']"),
  1060 |       document.body
  1061 |     ].filter((root): root is Element => !!root);
  1062 |     for (const root of roots) {
  1063 |       const buttons = Array.from(root.querySelectorAll<HTMLButtonElement>("button"))
  1064 |         .filter((button) => {
  1065 |           const text = `${button.innerText} ${button.getAttribute("aria-label") ?? ""} ${button.title ?? ""}`.trim();
  1066 |           const rect = button.getBoundingClientRect();
  1067 |           return matcher.test(text) && rect.width > 0 && rect.height > 0 && !button.disabled;
  1068 |         });
  1069 |       const button = buttons.at(-1);
  1070 |       if (button) {
  1071 |         button.click();
  1072 |         return true;
  1073 |       }
  1074 |     }
  1075 |     return false;
  1076 |   }, { source: name.source, flags: name.flags });
  1077 |   if (clicked) {
  1078 |     await page.waitForTimeout(700);
  1079 |     return;
  1080 |   }
  1081 |
  1082 |   let buttons = activeRoot(page).getByRole("button", { name });
  1083 |   if ((await buttons.count().catch(() => 0)) === 0) {
  1084 |     buttons = page.getByRole("button", { name });
  1085 |   }
> 1086 |   await expect(buttons.first(), `${name} 버튼이 보여야 합니다.`).toBeVisible({ timeout: 10_000 });
       |                                                         ^ Error: /^마감$/ 버튼이 보여야 합니다.
  1087 |   const count = await buttons.count();
  1088 |   for (let index = count - 1; index >= 0; index -= 1) {
  1089 |     const button = buttons.nth(index);
  1090 |     if (await button.isVisible().catch(() => false)) {
  1091 |       await button.click();
  1092 |       return;
  1093 |     }
  1094 |   }
  1095 |   throw new Error(`${name} 버튼을 클릭할 수 없습니다.`);
  1096 | }
  1097 |
  1098 | async function visibleText(page: Page): Promise<string> {
  1099 |   return normalizeText(await page.locator("body").innerText({ timeout: 10_000 }));
  1100 | }
  1101 |
  1102 | function activeRoot(page: Page) {
  1103 |   return page.locator("[data-route-keep-alive-active='true']").last();
  1104 | }
  1105 |
  1106 | async function attachEvidenceLog(
  1107 |   testInfo: TestInfo,
  1108 |   pageName: string,
  1109 |   role: string,
  1110 |   suffix: string,
  1111 |   lines: string[]
  1112 | ): Promise<void> {
  1113 |   await testInfo.attach(`${safeFilename(pageName)}-${safeFilename(role)}-${safeFilename(suffix)}.md`, {
  1114 |     body: lines.join("\n"),
  1115 |     contentType: "text/markdown"
  1116 |   });
  1117 | }
  1118 |
  1119 | async function attachPageScreenshot(
  1120 |   page: Page,
  1121 |   testInfo: TestInfo,
  1122 |   pageName: string,
  1123 |   role: string,
  1124 |   suffix: string
  1125 | ): Promise<void> {
  1126 |   const screenshotPath = testInfo.outputPath(`${safeFilename(pageName)}-${safeFilename(role)}-${safeFilename(suffix)}.png`);
  1127 |   await page.screenshot({ path: screenshotPath, fullPage: false, timeout: 10_000 });
  1128 |   await testInfo.attach(`${safeFilename(pageName)}-${safeFilename(role)}-${safeFilename(suffix)}.png`, {
  1129 |     path: screenshotPath,
  1130 |     contentType: "image/png"
  1131 |   });
  1132 | }
  1133 |
  1134 | async function stopAndAttachTrace(
  1135 |   context: BrowserContext,
  1136 |   tracePath: string,
  1137 |   traceOptions: TraceOptions
  1138 | ): Promise<void> {
  1139 |   await context.tracing.stop({ path: tracePath }).catch(() => undefined);
  1140 |   if (!fs.existsSync(tracePath)) {
  1141 |     return;
  1142 |   }
  1143 |   await traceOptions.testInfo.attach(`${safeFilename(traceOptions.pageName)}-${safeFilename(traceOptions.role)}-${safeFilename(traceOptions.suffix)}-trace.zip`, {
  1144 |     path: tracePath,
  1145 |     contentType: "application/zip"
  1146 |   });
  1147 | }
  1148 |
  1149 | function getRuntimeBlockReason(runtimeEnv: RuntimeQaEnv): string | undefined {
  1150 |   const appKey = runtimeEnv.application.toUpperCase();
  1151 |   const envKey = runtimeEnv.environment.toUpperCase();
  1152 |
  1153 |   if (!runtimeEnv.baseUrl) {
  1154 |     return `BIX_${appKey}_${envKey}_BASE_URL 또는 BIX_${appKey}_${envKey}_LOGIN_URL이 필요합니다.`;
  1155 |   }
  1156 |   if (!runtimeEnv.tenantId) {
  1157 |     return `BIX_${appKey}_${envKey}_TENANT_ID가 필요합니다.`;
  1158 |   }
  1159 |   if (!runtimeEnv.storageState && (!runtimeEnv.username || !runtimeEnv.password || !runtimeEnv.loginUrl)) {
  1160 |     return `${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role} role의 storageState 또는 로그인 계정 정보가 필요합니다.`;
  1161 |   }
  1162 |   return undefined;
  1163 | }
  1164 |
  1165 | function findPageDefinition(pages: QaPageDefinition[], name: string): QaPageDefinition {
  1166 |   const pageDefinition = pages.find((page) => page.name === name || page.name.includes(name));
  1167 |   if (!pageDefinition) {
  1168 |     throw new Error(`checklist.json pages에 '${name}' 페이지 정의가 필요합니다.`);
  1169 |   }
  1170 |   return pageDefinition;
  1171 | }
  1172 |
  1173 | function apiBaseFromRuntime(runtimeEnv: RuntimeQaEnv): string {
  1174 |   const baseUrl = runtimeEnv.baseUrl;
  1175 |   if (!baseUrl) {
  1176 |     throw new Error("baseUrl이 필요합니다.");
  1177 |   }
  1178 |   const url = new URL(baseUrl);
  1179 |   url.hostname = url.hostname.replace(/^(admin|store)-/, "api-");
  1180 |   return url.origin;
  1181 | }
  1182 |
  1183 | function formatNumber(value: number): string {
  1184 |   return Number(value).toLocaleString("ko-KR");
  1185 | }
  1186 |
```