# 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: 3839/dev/user-preset-bulk-department-admin.spec.ts >> [3839][dev] [QA] 역할별 사용자 프리셋 일괄 변경 및 부서 관리 검증 / 문서보관함 엑셀 재QA >> 가맹점관리에서 박재민_가맹 검색 후 문서보관함 엑셀 다운로드 파일을 그대로 업로드
- Location: src/qa/scenarios/departmentExcelDocumentRetest.ts:118:7

# Error details

```
Error: 가맹점관리 엑셀 업로드에서 박재민_가맹 대상 row가 성공 처리되어야 합니다: name=박재민_가맹 action= error=정산주기은(는) 목록에 있는 값만 입력 가능합니다.

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false
```

# Test source

```ts
  25  |   key: keyof Fixtures["excelTargets"];
  26  |   page: QaPageDefinition;
  27  |   targetName: string;
  28  |   downloadMenu: RegExp;
  29  |   uploadMenu: RegExp;
  30  |   fileNameIncludes: RegExp;
  31  |   excelEndpoint: string;
  32  |   uploadType: string;
  33  |   listEndpoint: string;
  34  |   filterParamKey: string;
  35  | }
  36  |
  37  | interface ExcelEvidence {
  38  |   target: DepartmentExcelTarget;
  39  |   fileName: string;
  40  |   filePath: string;
  41  |   headers: string[];
  42  |   rows: string[][];
  43  |   businessTypeValue: string;
  44  |   uploadText: string;
  45  |   targetUploadSuccess: boolean;
  46  |   targetUploadMessage: string;
  47  |   postUploadText: string;
  48  |   documentPageText: string;
  49  | }
  50  |
  51  | const ROLE = "ADMIN";
  52  |
  53  | const DEFAULT_FIXTURES: Fixtures = {
  54  |   excelTargets: {
  55  |     corporation: "박재민_법인",
  56  |     branch: "박재민_영업",
  57  |     merchant: "박재민_가맹"
  58  |   }
  59  | };
  60  |
  61  | export function runDepartmentExcelDocumentRetestSuite(options: ScenarioOptions): void {
  62  |   const metadata = loadIssueMetadata(options.issueId);
  63  |   const checklist = loadChecklist(options.issueId, options.environment);
  64  |   const fixtures = {
  65  |     ...DEFAULT_FIXTURES,
  66  |     ...((checklist as QaChecklist & { fixtures?: Partial<Fixtures> }).fixtures ?? {}),
  67  |     excelTargets: {
  68  |       ...DEFAULT_FIXTURES.excelTargets,
  69  |       ...((checklist as QaChecklist & { fixtures?: Partial<Fixtures> }).fixtures?.excelTargets ?? {})
  70  |     }
  71  |   };
  72  |   const runtimeEnv = getRuntimeQaEnv(options.environment, "admin", ROLE);
  73  |   const blockReason = getRuntimeBlockReason(runtimeEnv);
  74  |   const targets: DepartmentExcelTarget[] = [
  75  |     {
  76  |       key: "corporation",
  77  |       page: findPage(checklist.pages, "총판관리"),
  78  |       targetName: fixtures.excelTargets.corporation,
  79  |       downloadMenu: /법인\s*엑셀\s*다운로드/,
  80  |       uploadMenu: /법인\s*일괄\s*등록/,
  81  |       fileNameIncludes: /법인|총판|corporation/i,
  82  |       excelEndpoint: "corporations",
  83  |       uploadType: "corporations",
  84  |       listEndpoint: "corporations",
  85  |       filterParamKey: "corporationIds"
  86  |     },
  87  |     {
  88  |       key: "branch",
  89  |       page: findPage(checklist.pages, "영업점관리"),
  90  |       targetName: fixtures.excelTargets.branch,
  91  |       downloadMenu: /영업점\s*엑셀\s*다운로드/,
  92  |       uploadMenu: /영업점\s*일괄\s*등록/,
  93  |       fileNameIncludes: /영업점|branch/i,
  94  |       excelEndpoint: "branch-offices",
  95  |       uploadType: "branch-offices",
  96  |       listEndpoint: "branch-offices",
  97  |       filterParamKey: "branchOfficeIds"
  98  |     },
  99  |     {
  100 |       key: "merchant",
  101 |       page: findPage(checklist.pages, "가맹점관리"),
  102 |       targetName: fixtures.excelTargets.merchant,
  103 |       downloadMenu: /가맹점\s*엑셀\s*다운로드/,
  104 |       uploadMenu: /가맹점\s*일괄\s*등록/,
  105 |       fileNameIncludes: /가맹점|merchant|vendor/i,
  106 |       excelEndpoint: "vendors",
  107 |       uploadType: "vendors",
  108 |       listEndpoint: "vendors",
  109 |       filterParamKey: "vendorIds"
  110 |     }
  111 |   ];
  112 |   const evidence = new Map<DepartmentExcelTarget["key"], ExcelEvidence>();
  113 |
  114 |   test.describe(`[${options.issueId}][${options.environment}] ${metadata.subject} / 문서보관함 엑셀 재QA`, () => {
  115 |     test.skip(!!blockReason, blockReason);
  116 |
  117 |     for (const target of targets) {
  118 |       test(`${target.page.name}에서 ${target.targetName} 검색 후 문서보관함 엑셀 다운로드 파일을 그대로 업로드`, async ({ browser }, testInfo) => {
  119 |         test.setTimeout(240_000);
  120 |         const result = await runTargetExcelFlow(browser, runtimeEnv, target, testInfo);
  121 |         evidence.set(target.key, result);
  122 |         writeEvidence(options.issueId, options.environment, target, result);
  123 |         expect(result.headers.join(" "), `${target.page.name} 엑셀에 사업자 구분 컬럼이 있어야 합니다.`).toMatch(/사업자\s*구분|사업자구분/);
  124 |         expect(result.businessTypeValue, `${target.page.name} 엑셀의 ${target.targetName} 행에 사업자 구분 선택값이 있어야 합니다.`).not.toBe("");
> 125 |         expect(result.targetUploadSuccess, `${target.page.name} 엑셀 업로드에서 ${target.targetName} 대상 row가 성공 처리되어야 합니다: ${result.targetUploadMessage}`).toBe(true);
      |                                                                                                                                                     ^ Error: 가맹점관리 엑셀 업로드에서 박재민_가맹 대상 row가 성공 처리되어야 합니다: name=박재민_가맹 action= error=정산주기은(는) 목록에 있는 값만 입력 가능합니다.
  126 |         expect(result.postUploadText, `${target.page.name} 업로드 후 대상 데이터가 목록에 유지되어야 합니다.`).toContain(target.targetName);
  127 |       });
  128 |     }
  129 |
  130 |     test(checklist.checklist[3], async () => {
  131 |       for (const target of targets) {
  132 |         const result = evidence.get(target.key) ?? readEvidence(options.issueId, options.environment, target);
  133 |         expect(result, `${target.page.name} 엑셀 다운로드/업로드 선행 검증이 완료되어야 합니다.`).toBeTruthy();
  134 |         expect(result!.headers.join(" "), `${target.page.name} 엑셀에 사업자 구분 컬럼이 있어야 합니다.`).toMatch(/사업자\s*구분|사업자구분/);
  135 |         expect(result!.businessTypeValue, `${target.page.name} 엑셀의 사업자 구분 선택값이 있어야 합니다.`).not.toBe("");
  136 |       }
  137 |     });
  138 |
  139 |     test(checklist.checklist[4], async () => {
  140 |       for (const target of targets) {
  141 |         const result = evidence.get(target.key) ?? readEvidence(options.issueId, options.environment, target);
  142 |         expect(result, `${target.page.name} 업로드 후 목록 재확인 근거가 있어야 합니다.`).toBeTruthy();
  143 |         expect(result!.postUploadText, `${target.page.name} 업로드 완료 후 대상 데이터가 정상 유지되어야 합니다.`).toContain(target.targetName);
  144 |       }
  145 |     });
  146 |   });
  147 | }
  148 |
  149 | async function runTargetExcelFlow(
  150 |   browser: Browser,
  151 |   runtimeEnv: RuntimeQaEnv,
  152 |   target: DepartmentExcelTarget,
  153 |   testInfo: TestInfo
  154 | ): Promise<ExcelEvidence> {
  155 |   let result!: ExcelEvidence;
  156 |   await withRolePage(browser, runtimeEnv, async (page) => {
  157 |     await openTargetPage(page, runtimeEnv, target);
  158 |     await applyDepartmentSearch(page, target);
  159 |     await attachScreenshot(page, testInfo, target, "filtered-list");
  160 |
  161 |     const documentId = await requestExcelByApi(runtimeEnv, target, testInfo);
  162 |     const documentPageText = await openDocumentHistory(page, runtimeEnv, target, documentId, testInfo);
  163 |     const downloaded = await downloadDocumentExcel(runtimeEnv, documentId, target, testInfo);
  164 |     const parsed = parseDepartmentExcel(downloaded.buffer, downloaded.fileName, target);
  165 |     const uploadResult = await uploadDownloadedExcel(runtimeEnv, target, downloaded.buffer, downloaded.fileName, testInfo);
  166 |     await openTargetPage(page, runtimeEnv, target);
  167 |     await applyDepartmentSearch(page, target);
  168 |     const postUploadText = normalize(await page.locator("body").innerText({ timeout: 10_000 }));
  169 |     await attachScreenshot(page, testInfo, target, "after-upload");
  170 |     await attachLog(testInfo, target, "excel-document-retest", [
  171 |       `화면: ${target.page.name}`,
  172 |       `검색 대상: ${target.targetName}`,
  173 |       `문서 ID: ${documentId}`,
  174 |       `파일명: ${downloaded.fileName}`,
  175 |       `헤더: ${parsed.headers.join(" | ")}`,
  176 |       `사업자 구분 선택값: ${parsed.businessTypeValue}`,
  177 |       `문서보관함 화면 일부: ${snippet(documentPageText, 2000)}`,
  178 |       `업로드 대상 row 성공 여부: ${uploadResult.targetUploadSuccess}`,
  179 |       `업로드 대상 row 메시지: ${uploadResult.targetUploadMessage}`,
  180 |       `업로드 응답 일부: ${snippet(uploadResult.text, 2500)}`,
  181 |       `목록 재확인 일부: ${snippet(postUploadText, 2500)}`
  182 |     ]);
  183 |     result = {
  184 |       target,
  185 |       fileName: downloaded.fileName,
  186 |       filePath: downloaded.filePath,
  187 |       headers: parsed.headers,
  188 |       rows: parsed.rows,
  189 |       businessTypeValue: parsed.businessTypeValue,
  190 |       uploadText: uploadResult.text,
  191 |       targetUploadSuccess: uploadResult.targetUploadSuccess,
  192 |       targetUploadMessage: uploadResult.targetUploadMessage,
  193 |       postUploadText,
  194 |       documentPageText
  195 |     };
  196 |   });
  197 |   return result;
  198 | }
  199 |
  200 | function evidencePath(issueId: string, environment: QaEnvironment, target: DepartmentExcelTarget): string {
  201 |   return path.join(process.cwd(), "qa-results", "issues", issueId, environment, "evidence", `${target.key}.json`);
  202 | }
  203 |
  204 | function writeEvidence(issueId: string, environment: QaEnvironment, target: DepartmentExcelTarget, result: ExcelEvidence): void {
  205 |   const file = evidencePath(issueId, environment, target);
  206 |   fs.mkdirSync(path.dirname(file), { recursive: true });
  207 |   fs.writeFileSync(file, JSON.stringify({
  208 |     fileName: result.fileName,
  209 |     filePath: result.filePath,
  210 |     headers: result.headers,
  211 |     rows: result.rows,
  212 |     businessTypeValue: result.businessTypeValue,
  213 |     uploadText: result.uploadText,
  214 |     targetUploadSuccess: result.targetUploadSuccess,
  215 |     targetUploadMessage: result.targetUploadMessage,
  216 |     postUploadText: result.postUploadText,
  217 |     documentPageText: result.documentPageText
  218 |   }, null, 2), "utf8");
  219 | }
  220 |
  221 | function readEvidence(issueId: string, environment: QaEnvironment, target: DepartmentExcelTarget): ExcelEvidence | undefined {
  222 |   const file = evidencePath(issueId, environment, target);
  223 |   if (!fs.existsSync(file)) return undefined;
  224 |   const value = JSON.parse(fs.readFileSync(file, "utf8")) as Omit<ExcelEvidence, "target">;
  225 |   return { ...value, target };
```