전체화면 명령 입력의 제출 버튼을 primary로 구분

하단 `입력`이 막대 위 글자로만 보여 제출인지 알기 어려웠고 녹색 상단 버튼이 제출처럼
보였다. Ref처럼 녹색은 이동(명령 취소)에만 두고, 제출은 명령명을 붙인 primary 버튼
(`화계 입력`)으로, `명령 다시 선택`은 secondary로 낮춘다. e2e locator와 버튼 색·
geometry assertion을 함께 갱신한다.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 14:04:49 +00:00
co-authored by Claude Opus 5.5
parent a373ed327d
commit b90dd4499d
6 changed files with 72 additions and 28 deletions
+49 -20
View File
@@ -1237,7 +1237,7 @@ test('shows and reserves the Ref spy command for a user on desktop and mobile',
);
await form.locator('select').selectOption('2');
await picker.screenshot({ path: test.info().outputPath('spy-command-desktop-1200.png') });
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(editor.locator('.action-column > div').first()).toHaveText('【허창】에 첩보 실행');
expect(JSON.stringify(requests)).toContain('"action":"che_첩보","args":{"destCityId":2}');
@@ -1355,7 +1355,7 @@ test('defaults founding to a Ref-selectable nation trait and opens colored optio
await mobilePicker.getByRole('button', { name: '건국', exact: true }).click();
await mobilePicker.getByLabel('국가명').fill('가나다라마바사아자차');
await expect(mobilePicker.getByText('국가명은 전각 9자 또는 반각 18자 이하여야 합니다.')).toBeVisible();
await expect(mobilePicker.getByRole('button', { name: '입력', exact: true })).toBeDisabled();
await expect(mobilePicker.getByRole('button', { name: / 입력$/ })).toBeDisabled();
await mobilePicker.getByLabel('국가명').fill('신국');
const mobileColorType = mobilePicker.getByLabel('국기 색상');
await mobileColorType.click();
@@ -1384,7 +1384,7 @@ test('defaults founding to a Ref-selectable nation trait and opens colored optio
expect(colorSelectRect.right).toBeLessThanOrEqual(500);
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(500);
await mobilePicker.screenshot({ path: test.info().outputPath('founding-colored-option-mobile-500.png') });
await mobilePicker.getByRole('button', { name: '입력', exact: true }).click();
await mobilePicker.getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="general"] .action-column > div').first()).toContainText('신국');
await expect.poll(() => JSON.stringify(requests)).toContain('"colorType":15');
});
@@ -1499,7 +1499,7 @@ test('shows speciality reset cooldowns and reserves special user commands from t
const forceMoveForm = picker.getByTestId('command-argument-form');
await expect(forceMoveForm.getByTestId('command-argument-guidance')).toContainText('선택한 도시로 강행합니다.');
await forceMoveForm.locator('select').selectOption('2');
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(editor.locator('.action-column > div').nth(2)).toHaveText('【허창】으로 강행');
const serialized = JSON.stringify(requests);
@@ -1669,7 +1669,7 @@ test('keeps general and chief command categories after input and across page rel
await picker.getByRole('button', { name: '군사', exact: true }).click();
await picker.getByRole('button', { name: '출병', exact: true }).click();
await picker.getByTestId('command-argument-form').locator('select').selectOption('3');
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(generalEditor.locator('.action-column > div').first()).toHaveText('【단양】으로 출병');
await generalEditor.getByRole('button', { name: '2턴 명령 입력', exact: true }).click();
@@ -2032,7 +2032,7 @@ test('enters general and nation command arguments and sends exact values', async
const rect = area.getBoundingClientRect();
return { width: rect.width, height: rect.height };
});
await page.getByTestId('command-picker').getByRole('button', { name: '입력', exact: true }).click();
await page.getByTestId('command-picker').getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="general"] .action-column > div').first()).toHaveText(
'【허창】에 화계실행'
);
@@ -2059,7 +2059,7 @@ test('enters general and nation command arguments and sends exact values', async
fontSize: style.fontSize,
};
});
await chiefPicker.getByRole('button', { name: '입력', exact: true }).click();
await chiefPicker.getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="nation"] .action-column > div').first()).toHaveText(
'【여포NPC】 쌀 300 포상'
);
@@ -2184,7 +2184,7 @@ test('uses a Ref-style full recruitment page without horizontal overflow on desk
await infantry.getByRole('button', { name: '절반', exact: true }).click();
await expect(estimatedGold).toHaveText('363금');
await page.screenshot({ path: testInfo.outputPath('recruitment-desktop.png') });
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="general"] .action-column > div').first()).toHaveText(
'【보병】 3500명 징병'
);
@@ -2307,7 +2307,7 @@ test('uses a Ref-style full recruitment page without horizontal overflow on desk
await unavailableMercenary.click();
await mercenaryForm.locator('.mobile-selected-panel input[type=number]').fill('25');
await expect(mercenaryForm.locator('.mobile-selected-panel .submit-recruit')).toBeEnabled();
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="general"] .action-column > div').nth(1)).toHaveText(
'【정예병】 2500명 모병'
);
@@ -2593,7 +2593,7 @@ test('limits non-aggression end years to the Ref twenty-year window on desktop a
await expect(yearInput).toHaveAttribute('max', '210');
await expect(yearInput).toHaveValue('191');
const submit = picker.getByRole('button', { name: '입력', exact: true });
const submit = picker.getByRole('button', { name: / 입력$/ });
await yearInput.fill('211');
await expect(submit).toBeDisabled();
await yearInput.fill('210');
@@ -2881,7 +2881,7 @@ test('offers Ref amount presets and rich, command-specific general lists', async
await expect(generalList.locator('.target-option strong')).toHaveText(['장수 (업)', '관우 (업)', '여포NPC (업)']);
await generalList.locator('.target-option').filter({ hasText: '여포NPC' }).click();
const awardResponse = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await awardResponse;
await page.goto('/che/chief-center');
@@ -2946,7 +2946,7 @@ test('offers Ref amount presets and rich, command-specific general lists', async
await expect(tupleInputs.nth(0)).toHaveValue('20000');
await expect(tupleInputs.nth(1)).toHaveValue('1370');
const aidResponse = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await aidResponse;
const serialized = JSON.stringify(requests);
@@ -3088,7 +3088,7 @@ for (const width of [1200, 500]) {
);
await picker.screenshot({ path: testInfo.outputPath('assignment-details.png') });
const saved = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await saved;
expect(JSON.stringify(requests)).toContain('"destGeneralId":3');
expect(JSON.stringify(requests)).toContain('"destCityId":2');
@@ -3163,7 +3163,7 @@ test('keeps the entered command visible and reports a server validation error',
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
await page.getByTestId('command-picker').getByRole('button', { name: /화계/ }).click();
await page.getByTestId('command-argument-form').locator('select').selectOption('2');
const submit = page.getByTestId('command-picker').getByRole('button', { name: '입력', exact: true });
const submit = page.getByTestId('command-picker').getByRole('button', { name: / 입력$/ });
await submit.click();
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
@@ -3212,7 +3212,7 @@ test('keeps Ref command briefs and autonomous-action state after a turn mutation
await picker.getByRole('button', { name: '군사', exact: true }).click();
await picker.getByRole('button', { name: '출병', exact: true }).click();
await picker.getByTestId('command-argument-form').locator('select').selectOption('3');
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(firstRow).toHaveText('【단양】으로 출병');
await expect(firstRow).toHaveAttribute('data-autorun-tooltip', / : 200 3 · .*/u);
@@ -3305,7 +3305,7 @@ test('uses drag selection, clipboard paste, and a stored template in advanced mo
await expect(blockedFire).toBeEnabled();
await blockedFire.click();
await picker.getByTestId('command-argument-form').locator('select').selectOption('2');
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect(editor.locator('.action-column > div').nth(2)).toHaveText('【허창】에 화계실행');
const recentMenu = editor.locator('details').filter({ has: page.getByText('최근 실행', { exact: true }) });
@@ -3495,7 +3495,7 @@ test('keeps the shared main and chief shell geometry and interaction states', as
await chiefArgumentForm.getByRole('button', { name: '쌀' }).click();
await chiefArgumentForm.locator('input[type=number]').fill('300');
await chiefArgumentForm.locator('select').selectOption('2');
await page.getByTestId('command-picker').getByRole('button', { name: '입력', exact: true }).click();
await page.getByTestId('command-picker').getByRole('button', { name: / 입력$/ }).click();
await expect(page.locator('[data-command-scope="nation"] .action-column > div').first()).toHaveText(
'【관우】 쌀 300 포상'
);
@@ -3748,7 +3748,7 @@ for (const width of [1200, 390]) {
await expect(input).toHaveCount(0);
await expect(results.locator('button')).toHaveCount(3);
await expect(form.locator('#command-arg-destCityId')).toHaveValue('2');
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect.poll(() => JSON.stringify(requests)).toContain('"destCityId":2');
await page.goto(gamePath('/chief-center'));
@@ -3794,7 +3794,7 @@ for (const width of [1200, 390]) {
expect(geometry.viewport.scale).toBe(initialScale);
await writeFile(testInfo.outputPath(`search-${width}.json`), JSON.stringify(geometry, null, 2));
await form.screenshot({ path: testInfo.outputPath(`search-${width}.png`) });
await picker.getByRole('button', { name: '입력', exact: true }).click();
await picker.getByRole('button', { name: / 입력$/ }).click();
await expect.poll(() => JSON.stringify(requests)).toContain('"destGeneralId":2,"destCityId":2');
await page.reload();
await page.getByRole('button', { name: '2턴 명령 입력', exact: true }).click();
@@ -3976,8 +3976,20 @@ for (const viewport of [
bar: rect(bar),
buttons: [...bar.querySelectorAll('button')].map((button) => ({
text: button.textContent?.trim(),
background: getComputedStyle(button).backgroundColor,
...button.getBoundingClientRect().toJSON(),
})),
submit: (() => {
const submit = element.querySelector<HTMLButtonElement>('.picker-actions > .command-submit');
if (!submit) return null;
return {
text: submit.textContent?.trim(),
disabled: submit.disabled,
background: getComputedStyle(submit).backgroundColor,
fontWeight: getComputedStyle(submit).fontWeight,
...submit.getBoundingClientRect().toJSON(),
};
})(),
title: bar.querySelector('h2')?.textContent?.replace(/\s+/g, ' ').trim(),
map: rect(map),
fields: rect(fields),
@@ -4003,6 +4015,23 @@ for (const viewport of [
expect(geometry.bar?.y).toBe(0);
expect(geometry.buttons.map((button) => button.text)).toEqual(['명령 취소', '명령 다시 선택', '검색 꺼짐']);
expect(geometry.title).toBe('화계1턴');
// 녹색은 Ref 돌아가기와 같은 이동 버튼(명령 취소)에만 쓰고, 제출은 명령명을 붙인 primary(파랑) 버튼이다.
expect(geometry.buttons.map((button) => button.background)).toEqual([
'rgb(0, 88, 44)',
'rgb(68, 68, 68)',
'rgb(68, 68, 68)',
]);
expect(geometry.submit).toMatchObject({
text: '화계 입력',
disabled: false,
background: 'rgb(55, 90, 127)',
fontWeight: '700',
});
expect(geometry.submit!.width).toBe(Math.min(360, viewport.width - 12));
expect(geometry.submit!.x + geometry.submit!.width / 2).toBeCloseTo(viewport.width / 2, 0);
expect(geometry.submit!.height).toBeGreaterThanOrEqual(40);
expect(geometry.submit!.bottom).toBeLessThanOrEqual(viewport.height);
expect(geometry.submit!.bottom).toBeGreaterThan(viewport.height - 12);
expect(geometry.bodyOverflow).toBe('hidden');
expect(geometry.scrollWidth).toBe(geometry.clientWidth);
for (const button of geometry.buttons) {
@@ -4063,7 +4092,7 @@ for (const viewport of [
await listPicker.getByRole('button', { name: /화계/ }).click();
overlay = page.getByRole('dialog', { name: '화계 4턴 명령 입력' });
await overlay.getByTestId('command-argument-form').locator('select').selectOption('2');
await overlay.getByRole('button', { name: '입력', exact: true }).click();
await overlay.getByRole('button', { name: / 입력$/ }).click();
await expect(page.getByTestId('command-picker')).toHaveCount(0);
await expect(editor.locator('.action-column > div').nth(3)).toHaveText('【허창】에 화계실행');
await expect