웹 내의 요소를 인식하거나 요소에 접근하는 방식은 여러가지가 있다.
CSS 선택자나 경로를 이용하여 요소에 접근하게 되면 DOM 구조나 클래스가 변하면 코드가 작동하지 않는다.
때문에 인터랙티브 요소는 get_by_role, 폼 입력은 get_by_label / get_by_placeholder, 정적 텍스트 확인은 get_by_text를 우선 사용하고, 불가피할 때 get_by_test_id, 마지막으로 짧고 안정적인 CSS로 보완할 것을 권장한다.
이후 filter를 함께 사용하여 모호성을 해소하는 것이 효과적이다.
페이지 내 요소의 ARIA role(역할)과 이름으로 요소를 찾아 로케이터를 반환한다.
ARIA role
기본적으로는 WAI-ARIA 1.2에 근거하며 button/label/input/a/h1-h6 등은 HTML 시맨틱으로 암묵적 role이 부여되기도 한다.
이름
문자열 또는 정규식으로 매칭, 보이는 텍스트나 aria-label 등 접근성 이름 소스에서 계산된 값을 기준으로 한다.
상태 / 속성 또한 함께 필터링할 수 있다.
page.get_by_role(role, { name, level, checked, selected, pressed, expanded, disabled, includeHidden, exact })
역할 예시
'button', 'link', 'checkbox', 'textbox', 'heading', 'tab', 'tabpanel', 'list', 'listitem' 등
공식 메뉴얼 링크
role: 찾을 요소의 ARIA 역할을 지정하는 필수 인자
name: 문자열 또는 정규식으로 매칭. 소스는 텍스트 콘텐츠, aria-label, aria-labelledby, label 연계 등이며 사용자에게 읽히는 이름과 동일
level: 헤딩 전용 필터로 h1~h6를 1~6으로 지정한다. 예: getByRole('heading', { level: 2 })는 h2에 해당
checked: 체크박스/스위치/라디오 등에 대해 체크 상태 필터링
selected: 탭/옵션/리스트 항목 등 선택 상태가 있는 위젯의 선택 여부로 필터링
pressed: 토글 버튼의 눌림 상태를 필터링
expanded: 아코디언/디스클로저/트리 등 확장 가능한 위젯의 펼침 상태를 필터링
disabled: 비활성화된 요소 포함 여부를 필터링
include_hidden: 숨김 요소까지 포함할지를 선택
exact: name 문자열 매칭을 완전 일치(대소문자 포함)로 강제한다. 정규식 name에는 적용되지 않으며 공백 트리밍 후 비교
버튼 클릭 : 이름 변동에 대비해 정규식 사용.
await page.get_by_role("button", name=re.compile("제출|submit", re.I)).click()
체크된 스위치 찾기
await page.get_by_role("switch", name="다크 모드", checked=True).click()→현재 켜진 스위치만 타겟
탭 전환(선택 상태 활용)
await page.get_by_role("tab", name="설정").click()
await expect(page.get_by_role("tab", name="설정", selected=True)).to_be_visible()
아코디언 펼침 상태
section = page.get_by_role("button", name="고객센터")
await section.click()
await expect(section).to_have_attribute("aria-expanded", "true")
헤딩 레벨 특정
await expect(page.get_by_role("heading", name="회원가입", level=3)).to_be_visible() → h3를 정확히 지정.
정확 매칭 강제
page.get_by_role("button", name="삭제", exact=True)→ '삭제하기' 같은 부분 일치 후보를 배제.
입력 필드의 placeholder 속성 값으로 요소를 찾는다. 주로 라벨이 없거나 라벨 대신 플레이스홀더로 힌트를 제공하는 폼 입력을 안정적으로 선택할 때 사용한다.
placeholder는 사용자에게 보이는 힌트 문자열이므로, 텍스트 변경 가능성을 고려해 상황에 따라 정확 일치(exact=True) 또는 정규식을 병행한다.
text: 문자열 또는 정규식을 받을 수 있으며, 기본은 대소문자 무시의 부분 일치이다. 정규식 사용 시 유연한 매칭이 가능하다.
exact: True로 설정하면 대소문자 포함한 전체 문자열 일치로 강제한다. 정규식 사용 시에는 무시되며, 공백 정규화(앞뒤 공백 제거 등)는 여전히 적용된다.
기본 사용
await page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com") →라벨이 없는 이메일 입력에서 플레이스홀더로 필드를 지정하고 값을 채운다.
정확 일치 강제
await page.get_by_placeholder("Your Name", exact=True).fill("홍길동") →동일/유사 플레이스홀더가 여럿일 때 오탐을 줄인다.
정규식 매칭
await page.get_by_placeholder(re.compile(r"name@.+\.com", re.I)).fill("john@site.com") →플레이스홀더 문구 변형에 대비해 패턴으로 매칭한다.
프레임/영역 체이닝
frame = page.frame_locator("#signup-frame")
await frame.get_by_placeholder("Password").fill("secret") →특정 프레임/컨테이너 안의 입력만 타깃팅한다.
요소가 “내포한 텍스트”로 찾는다. 비인터랙티브 요소(div, span, p 등)의 가시 텍스트 확인/타깃팅에 적합하며, 버튼/링크 등 인터랙티브 요소에는 가급적 get_by_role을 우선 권장한다.
텍스트 매칭 시 Playwright는 공백 정규화를 수행한다. 여러 공백을 하나로, 줄바꿈을 공백으로 취급하며, 앞뒤 공백은 무시한다.
text: 문자열 또는 정규식으로 매칭한다. 문자열은 기본적으로 대소문자 무시의 부분 일치를 수행한다.
exact: True로 설정하면 대소문자 포함 전체 일치로 강제한다. 공백 정규화는 유지되므로 시각적으로 같은 텍스트면 안정적으로 매칭된다.
존재/가시성 검증
await expect(page.get_by_text("Welcome, John")).to_be_visible() →특정 문구가 렌더링됐는지 간결하게 확인한다.
정확 일치 강제
await expect(page.get_by_text("Brand:", exact=True)).to_be_visible() →부분 일치 후보(예: “Brand: New”)를 배제하고 정확한 라벨 형태만 매칭한다.
정규식 매칭(대소문자 무시)
await expect(page.get_by_text(re.compile(r"welcome,\s+john", re.I))).to_be_visible() →다국어/포맷 변화나 동적 공백에 유연하게 대응한다.
리스트에서 특정 항목 선택
await page.get_by_role("listitem").filter(has_text="orange").click()
await page.get_by_text("orange").click() →리스트 항목을 텍스트로 특정하거나, 먼저 역할로 좁히고 has_text로 필터링해 모호성을 줄인다.
컨테이너 체이닝 후 텍스트 검색
card = page.get_by_role("article", name="공지사항")
await card.get_by_text("자세히 보기").click() →상위 의미 블록을 먼저 고정한 뒤 내부 텍스트를 찾아 상호작용한다.
locator.filter(...)는 기존 로케이터 결과 집합을 추가 조건으로 “걸러내는” 메서드로, 텍스트 포함 여부(has_text/has_not_text), 특정 하위 요소 존재(has/has_not), 가시성(visible) 등을 조합해 단일 요소로 수렴시키는 데 사용된다.
필터 내부에 로케이터가 포함될 수 있다.
예시 :page.get_by_role(...).filter(has=page.get_by_role(.)).get_by_role(.).click()
필터링 시 내부 로케이터는 반드시 “외부 로케이터를 기준으로 한 상대 경로”로 평가되며, 서로 같은 프레임 안에 있어야 한다. 문서 루트 기준의 전역 탐색을 포함하면 실패한다.
has_text: 현재 요소 내부(자식·후손 포함)에 특정 텍스트가 “포함”되는 요소만 남긴다. 문자열은 기본적으로 대소문자 무시의 부분 일치이며, 정규식도 지원한다. 다국어·가변 문구에는 re.compile(..., re.I)를 권장한다.
has_not_text: 현재 요소 내부에 특정 텍스트가 “없음”을 조건으로 필터링한다. 재고 상태 등 특정 라벨을 제외해야 할 때 유용하며, 문자열 기본 동작은 has_text와 동일하고 정규식도 가능하다.
has: 현재 요소의 “후손” 중 주어진 로케이터가 매칭되는 경우만 남긴다. 내부 로케이터는 반드시 “외부 로케이터를 기준으로 상대적”이어야 하며 같은 프레임 안에서 평가된다. 전역 탐색을 섞으면 매칭이 실패한다.
has_not: 현재 요소의 후손 중 “특정 로케이터가 없음”을 조건으로 필터링한다. 교집합이 아니라 차집합을 만들 때 효과적이며, has와 동일하게 내부 로케이터는 상대적·동일 프레임 제약을 따른다.
visible: 가시성 기준으로 필터링한다. visible=True로 숨김 요소를 배제해 엄격 모드에서 다중 매칭을 빠르게 해소할 수 있다. 다만 가능하면 역할/이름/문맥으로 고유화하고, visible은 보조 수단으로 사용한다.
Filter by text
await page.get_by_role("listitem").filter(has_text="Product 2").get_by_role("button", name="Add to cart").click()
Filter by not having text
await expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
Filter by child/descendant
await page.get_by_role("listitem").filter(has=page.get_by_role("heading", name="Product 2")).get_by_role("button", name="Add to cart").click()
Filter by not having child/descendant
await expect(page.get_by_role("listitem").filter(has_not=page.get_by_role("heading", name="Product 2"))).to_have_count(1)
visible 필터와 조합
await page.locator("button").filter(visible=True).click()