본문으로 건너뛰기

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