본문으로 건너뛰기

3.1 Form, Keyboard, Pointer

한눈에 보기

  • 일반 text input은 fill()을 우선합니다.
  • key event 자체가 제품 기능이면 press() 또는 pressSequentially()를 사용합니다.
  • checkbox, radio는 check()/uncheck(), select는 selectOption()으로 의도를 표현합니다.
  • force: true는 test를 고치는 도구가 아니라 actionability 검사를 우회하는 마지막 수단입니다.
  • drag-and-drop은 dragTo()부터 시작하고, 제품이 세밀한 pointer event를 요구할 때만 mouse API로 내려갑니다.

Todo Lab의 form interaction 재현 화면

fill()과 실제 typing의 차이

fill()은 input을 focus하고 값을 설정하며 input event를 발생시킵니다. 대부분의 form test에는 이것이 더 빠르고 결정적입니다.

const email = page.getByLabel('이메일');
await email.fill('student@example.com');
await expect(email).toHaveValue('student@example.com');

자동 완성, keydown 단축키, 글자마다 동작하는 formatter처럼 keyboard event sequence가 요구될 때만 한 글자씩 입력합니다.

await page.getByLabel('검색').pressSequentially('postgres', { delay: 30 });
await page.getByLabel('검색').press('Enter');

delay를 flaky test 회피용 wait로 쓰면 안 됩니다. 제품 요구사항이 실제 key interval에 의존하지 않는다면 fill()이 더 적합합니다.

상태에 맞는 전용 action

await page.getByLabel('이용 약관에 동의').check();
await expect(page.getByLabel('이용 약관에 동의')).toBeChecked();

await page.getByLabel('지역').selectOption({ label: '서울' });
await expect(page.getByLabel('지역')).toHaveValue('seoul');

전용 API는 이미 원하는 상태인지 확인하고 필요한 경우에만 action합니다. 무조건 click하는 것보다 test intent와 failure message가 분명합니다.

Pointer와 modifier

await page.getByText('메뉴').hover();
await page.getByRole('menuitem', { name: '복제' }).click();

await page.getByRole('row', { name: /order-42/ }).click({
modifiers: ['ControlOrMeta'],
});

운영체제별 modifier 차이는 ControlOrMeta로 흡수할 수 있습니다. 좌표 click은 responsive layout에 취약하므로 semantic locator로 표현할 수 없을 때만 사용합니다.

Drag-and-drop

const card = page.getByRole('listitem', { name: '검토 중' });
const done = page.getByRole('region', { name: '완료' });
await card.dragTo(done);
await expect(done.getByText('검토 중')).toBeVisible();

제품이 dragover를 특정 횟수로 요구하는 경우 low-level mouse sequence가 필요할 수 있습니다. 이때도 source와 target bounding box를 고정 좌표로 복사하지 말고 실행 시 계산합니다.

실패 사례: programmatic click으로 문제 숨기기

// 사용자에게 button이 overlay로 가려져도 통과할 수 있다.
await page.getByRole('button', { name: '저장' }).dispatchEvent('click');

dispatchEvent()는 DOM event만 발생시키므로 visible, stable, receives-events 같은 조건을 검증하지 않습니다. 제품의 실제 사용성을 확인하려면 정상 click()을 사용합니다.

실습

  1. Todo Lab에서 할 일을 fill()로 추가합니다.
  2. 같은 흐름을 pressSequentially()로 바꾸고 trace의 event와 실행 시간을 비교합니다.
  3. 완료 checkbox를 click() 대신 check()로 바꿉니다.
  4. button 위에 overlay를 잠시 표시하고 정상 click이 기다리는지 관찰합니다.
  5. force: true를 적용했을 때 어떤 제품 결함을 놓치는지 기록합니다.

점검표

  • Action이 사용자 의도를 이름으로 드러내는가?
  • Keyboard event를 검증할 이유가 명확한가?
  • 고정 좌표나 styling class에 의존하지 않는가?
  • Action 뒤에 최종 제품 상태를 assertion하는가?

참고: Playwright Actions, Auto-waiting