프로필 로고
2026-04-08

Playwright Locator

Playwright의 Locator 개념과 권장 선택 방법을 정리한다. getByRole/getByLabel 등 접근성 기반 Locator 우선순위, filter를 이용한 요소 특정, and/or 조합 연산자, Strictness 규칙을 다룬다.

  • Playwright
  • testing
  • E2E

무엇인가?

  1. 웹 테스트를 작성할 때 가장 먼저 해결해야 할 문제는 "페이지에서 원하는 요소를 어떻게 찾을 것인가"이다.

  2. 전통적으로는 CSS 선택자나 XPath를 사용했지만, 이 방식은 DOM 구조가 바뀌면 테스트가 쉽게 깨지는 문제가 있었다.

  3. Playwright는 이 문제를 해결하기 위해 Locator라는 개념을 도입했다.

  4. Locator는 특정 순간에 페이지에서 요소를 찾는 방법을 표현하는 객체다.

  5. 핵심은 Locator가 액션을 실행할 때마다 DOM에서 요소를 새로 탐색한다는 점이다.

  6. 덕분에 렌더링 중 DOM이 바뀌더라도 항상 최신 요소를 대상으로 동작한다.

  7. 또한 Playwright는 요소가 준비될 때까지 자동으로 기다리는 auto-waiting과, 일시적 실패 시 재시도하는 retry-ability를 Locator 수준에서 기본으로 제공한다.

  8. 이 덕분에 테스트 코드에 sleep()이나 수동 대기 로직을 삽입할 필요가 없어진다.

  9. 즉, 비동기로 데이터가 로드되거나 인터랙션이 처리되는 상황에서도 Playwright가 타이밍을 알아서 맞춰주므로, 우리는 "언제 준비되는지"를 신경 쓰지 않고 테스트 시나리오 작성에만 집중할 수 있다.

권장 Locator 우선순위: 테스트할 요소 찾기

  1. Playwright는 사용자와 보조 기술이 페이지를 인식하는 방식에 가까운 Locator를 우선 사용하도록 권장한다.

  2. 가장 권장되는 것은 getByRole()로, 요소의 접근성 역할과 이름을 기준으로 찾는다.

  3. 예를 들어 "Sign in"이라는 이름의 버튼은 아래처럼 찾을 수 있다.

    await page.getByRole('button', { name: 'Sign in' }).click();
  4. 폼 요소는 getByLabel()로 연결된 label 텍스트를 기준으로 찾는 것이 적합하다.

    await page.getByLabel('Password').fill('secret');
  5. label이 없고 placeholder만 있는 inputgetByPlaceholder()를 사용한다.

    await page.getByPlaceholder('name@example.com').fill('test@test.com');
  6. 텍스트 내용으로 요소를 찾을 때는 getByText()를 쓰되, 버튼처럼 인터랙티브한 요소보다는 div, span, p 같은 비인터랙티브 요소에 적합하다.

    await expect(page.getByText('Welcome, John')).toBeVisible();
  7. 이미지처럼 alt 속성을 가진 요소는 getByAltText() 를 사용한다.

    await page.getByAltText('playwright logo').click();
  8. title 속성이 있는 요소는 getByTitle()을 쓴다.

    await expect(page.getByTitle('Issues count')).toHaveText('25 issues');
  9. 위의 방법으로 요소를 특정하기 어려울 때는 data-testid 속성을 HTML에 직접 부여하고 getByTestId()로 찾는 방식을 사용한다.

    await page.getByTestId('directions').click();
  10. CSS 선택자나 XPath는 DOM 구조에 강하게 결합되어 테스트가 쉽게 깨지므로 가장 마지막 수단으로만 사용해야 한다.

Locator 필터링: 테스트할 요소가 많을 경우 특정하기

  1. 페이지에 동일한 구조의 요소가 여러 개 있을 때, 원하는 요소 하나를 정확히 특정해야 한다.

  2. 이때 locator.filter()를 사용하면 기존 Locator 결과를 추가 조건으로 좁힐 수 있다.

  3. 예를 들어 "Product 2" 카드 안의 버튼만 클릭하려면 아래처럼 필터를 걸 수 있다.

    await page.getByRole('listitem')
      .filter({ hasText : 'Product 2' })
      .getByRole('button', { name: 'Add to cart' })
      .click();
  4. hasText는 해당 텍스트를 포함한 요소만, hasNotText는 포함하지 않은 요소만 선택한다.

  5. 텍스트뿐 아니라 특정 자식 Locator의 존재 여부로도 필터링할 수 있으며, hashasNot 옵션을 사용한다.

  6. 예를 들어 "Product 2"라는 heading을 자식으로 가진 listitem만 선택하려면 아래처럼 쓴다.

    await page.getByRole('listitem')
      .filter({ has: page.getByRole('heading', { name: 'Product 2' }) })
      .getByRole('button', { name: 'Add to cart' })
      .click();
  7. 이때 has 안에 전달하는 Locator는 바깥 Locator가 찾은 요소의 내부에서만 탐색이 시작된다.

  8. 즉, 위 예시에서 has 안의 Locator는 <li> 하나를 기준으로 그 안에서 heading을 찾는다.

  9. 따라서 <li> 바깥에 있는 요소를 has의 조건으로 쓰면, 탐색 범위를 벗어나기 때문에 항상 매칭에 실패한다.

  10. 아래 코드는 <ul> 전체를 조건으로 쓰기 때문에 <li> 안에서 <ul>을 찾으려 해서 동작하지 않는 잘못된 예시다.

    // ✖ 잘못된 예시
    await page.getByRole('listitem')
      .filter({ has: page.getByRole('list').getByText('Product 2') })
  11. 조건이 복잡할 경우 filter()를 여러 번 체이닝해서 단계적으로 좁혀나갈 수 있다.

    await rowLocator
      .filter({ hasText: 'Mary' })
      .filter({ has: page.getByRole('button', { name: 'Say goodbye' }) })
      .click();

Locator 조합 연산자: 여러 Locator로 찾기

  1. 두 Locator를 동시에 만족하는 요소를 찾고 싶을 때는 locator.and()를 사용한다.

  2. 예를 들어 role이 button이면서 title이 "Subscribe"인 요소를 아래처럼 찾을 수 있다.

    const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
  3. 반대로 두 Locator 중 하나라도 매칭되는 요소를 찾을 때는 locator.or()를 사용한다.

  4. 예를 들어 "New email" 버튼이 나타날 수도 있고, 보안 설정 팝업이 먼저 나타날 수도 있는 상황에서는 아래처럼 처리한다.

    const newEmail = page.getByRole('button', { name: 'New' });
    const dialog = page.getByText('Confirm security settings');
     
    await expect(newEmail.or(dialog).first()).toBeVisible();
     
    if (await dialog.isVisible())
      await page.getByRole('button', { name: 'Dismiss' }).click();
     
    await newEmail.click();
  5. or()로 연결된 두 요소가 동시에 화면에 존재하면, Playwright가 "어떤 요소에 액션을 해야 하는가"를 결정할 수 없어 오류를 던진다.

  6. 이 경우 .first()를 붙여 "둘 중 먼저 매칭되는 것을 선택하라"고 명시하면 오류를 방지할 수 있다.

Strictness와 주의사항

  1. Playwright의 Locator는 기본적으로 "하나의 액션은 하나의 요소에만 적용된다"는 엄격한 규칙을 따른다.

  2. 예를 들어 페이지에 버튼이 3개 있는데 getByRole('button').click()을 호출하면, Playwright는 "어느 버튼을 클릭해야 하는지 알 수 없다"며 오류를 던진다.

    // 버튼이 여러 개면 오류 발생
    await page.getByRole('button').click();
  3. 이 오류는 잘못된 코드를 실행하는 것을 막아주는 안전장치 역할을 한다.

  4. 반면 count()처럼 여러 요소를 대상으로 하는 메서드는 여러 요소가 매칭되어도 정상 동작한다.

    // 버튼이 여러 개여도 정상 동작
    await page.getByRole('button').count();
  5. 이 오류를 피하기 위해 .first(), .last(), .nth(1) 등으로 인덱스를 지정할 수 있다.

  6. 하지만 이 방식은 페이지 구조가 바뀌면 다른 요소를 선택할 수 있어 테스트가 잘못된 대상에 실행되는 위험이 있다.

  7. 올바른 해결 방법은 애초에 요소를 유일하게 특정할 수 있도록 Locator 자체를 더 정확하게 작성하는 것이다.

  8. 예를 들어 버튼이 여러 개라면, filter()accessible name을 추가해서 원하는 버튼 하나만 매칭되도록 좁혀야 한다.