Selenium VS Playwright
- #QA
- #Playwright
- #Selenium
- #ํ ์คํธ
- #ํ ์คํธ ์๋ํ
๋ค์ด๊ฐ๋ฉฐ
์๋ ํ์ธ์, ๋ ธ๋จธ์ค QAํ ์ต์ฐ์์ ๋๋ค.
QA ์ ๋ฌด๋ฅผ ํ๋ค ๋ณด๋ฉด ๊ฐ์ ํ ์คํธ๋ฅผ ๋ฐ๋ณตํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
ํนํ ๊ธฐ๋ฅ์ด ๋ง์์ง์๋ก ๊ธฐ์กด ๊ธฐ๋ฅ๋ค์ด ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ์ธํ๋ ํ๊ท ํ ์คํธ์ ์๊ฐ์ด ๋ง์ด ๋ค๊ณ , ๋๋ฝ๋๋ ๋ถ๋ถ๋ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋์ ๋ฐ๋ณต์ ์ธ ํ ์คํธ๋ ์๋ํ์ ๋งก๊ธฐ๊ณ , QA๋ ํ์์ ํ ์คํธ๋ ๋ณต์กํ ์๋๋ฆฌ์ค์ ์ง์คํ๊ณ ์ ํ์ต๋๋ค.
์ด๋ฌํ ๋ฐฐ๊ฒฝ์์ ์น ํ ์คํธ ์๋ํ๋ฅผ ๊ฒํ ํ๊ฒ ๋์๊ณ , ์ฌ๋ฌ ๋๊ตฌ๋ฅผ ๋น๊ตํ ๋์ Playwright๋ฅผ ์ ํํ์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ Selenium๊ณผ Playwright๋ฅผ ๋น๊ตํ๋ฉฐ ์ ํฌ๊ฐ Playwright๋ฅผ ์ ํํ ์ด์ ๋ฅผ ๊ณต์ ํด ๋ณด๋ ค ํฉ๋๋ค.
1. Selenium๊ณผ Playwright์ ๋ธ๋ผ์ฐ์ ์ ์ด ๋ฐฉ์
- Selenium
ํ
์คํธ ์ฝ๋ โ Selenium ํด๋ผ์ด์ธํธ โ HTTP โ ๋ธ๋ผ์ฐ์ ๋๋ผ์ด๋ฒ โ ๋ธ๋ผ์ฐ์
โ ๋งค๋ฒ ์์ฒญ/์๋ต ๋๊ธฐ
- Playwright
ํ
์คํธ ์ฝ๋ โ Playwright ๋๋ผ์ด๋ฒ โโ ๋ธ๋ผ์ฐ์
โ ํ ๋ฒ ์ฐ๊ฒฐ ํ ๊ณ์ ์ ์ง
WebDriver ๋ฐฉ์(Selenium) ์ ํด๋ฆญ, ์
๋ ฅ ๊ฐ์ ๋ช
๋ น์ ๋ณด๋ผ ๋๋ง๋ค HTTP ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๊ธฐ๋ค๋ฆฝ๋๋ค.
๋ฐ๋ฉด WebSocket ๋ฐฉ์(Playwright) ์ ๋ธ๋ผ์ฐ์ ์ ํ ๋ฒ ์ฐ๊ฒฐํ ํ ๊ณ์ ์ ์งํ๋ฉฐ ์๋ฐฉํฅ์ผ๋ก ํต์ ํ๊ธฐ ๋๋ฌธ์ ํ
์คํธ ์คํ์ด ๋ ๋น ๋ฆ
๋๋ค.
2. Selenium๊ณผ Playwright์ ๋๊ธฐ ์ฒ๋ฆฌ ๋ฐ ์์ ์ฑ
Selenium์ ๊ธฐ๋ณธ ์๋ ๋๊ธฐ๊ฐ ์ ํ์ ์ด๋ผ(์ค์ /์ ํธ์ด ์์ผ๋ฉด) ์์๊ฐ ๋ณด์ด๋์ง ์ฒดํฌํ ํ ๋ฐ๋ก ์ก์
์ ์ํํฉ๋๋ค.
๋ฐ๋ผ์ ๋ณ๋๋ก ๋๊ธฐ ์ค์ ์ ํ์ง ์์ผ๋ฉด ์์๊ฐ ๋ณด์ด์ง ์์ ๋ ์ฆ์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
๋ฐ๋ฉด Playwright๋ ์ก์
์ํ ์ ์ ์๋ ์์์ฒ๋ผ ํ์ํ ์กฐ๊ฑด๋ค์ ์๋์ผ๋ก ๊ฒ์ฆํ๊ณ ,
์ถฉ์กฑ๋ ๋๊น์ง ์๋ ๋๊ธฐ(Auto Wait) ํฉ๋๋ค.
๊ทธ๋์ ํ
์คํธ๊ฐ ๋ ์์ ์ ์ด๊ณ , Flakyํ ํ
์คํธ๊ฐ ํฌ๊ฒ ์ค์ด๋ญ๋๋ค.
์๋๋ ๊ฐ ํญ๋ชฉ์ ๋ํ ์ค๋ช
์
๋๋ค.
- Attached : DOM์ ์์๊ฐ ์์ฑ๋์๋๊ฐ
- Visible : CSS ๊ธฐ์ค์ผ๋ก ๋ณด์ด๋ ์ํ์ธ๊ฐ (display: none, width: 0, height: 0์ด ์๋์ง)
- Stable : ์์๊ฐ ์์ง์ด๊ฑฐ๋ ํฌ๊ธฐ๊ฐ ๋ณํ์ง ์๋ ์์ ์ ์ธ ์ํ์ธ๊ฐ
- Receiving events : ๋ค๋ฅธ ์์(์: ๋ชจ๋ฌ ์ค๋ฒ๋ ์ด)๊ฐ ํด๋น ์ง์ ์ ์ด๋ฒคํธ๋ฅผ ๋ง๊ณ ์์ง ์์์ง
- Enabled : ์์๊ฐ ํ์ฑํ๋ ์ํ์ธ๊ฐ (disabled๊ฐ ์๋์ง)
๋๊ธฐ ์ฒ๋ฆฌ ์ฝ๋ ๋น๊ต
Selenium V1 - time.sleep() ์ฌ์ฉ
๊ฐ์ฅ ๋จ์ํ์ง๋ง, ๋ฌด์กฐ๊ฑด n์ด๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋นํจ์จ์ ์ด๊ณ ๋ถ์์ ํ๋ฉฐ n์ด ์์ ํ์๋์ง ์์ผ๋ฉด ์คํจํฉ๋๋ค.
def test_login():
# ํ๋กฌ ์คํ ์ด ์ ์
driver = webdriver.Chrome()
driver.get("https://store.frommyarti.com/arti")
# ๋๋ณด๊ธฐ ๋ฉ๋ด ํด๋ฆญ
menu = driver.find_element(By.CSS_SELECTOR, 'img[alt="Main Menu"]')
menu.click()
# ์์๊ฐ ๋ณด์ผ ๋๊น์ง ๋๊ธฐ
# ๋ง์ฝ ๋คํธ์ํฌ ์ํ์ ๋ฐ๋ผ์ ์์๊ฐ ๋ฆ๊ฒ ํ์๋๋ค๋ฉด ํ
์คํธ๋ ์คํจํ๊ฒ ๋ฉ๋๋ค.
time.sleep(n)
# "๋ก๊ทธ์ธ ํ ์ด์ฉํด์ฃผ์ธ์." ๋ฌธ๊ตฌ ํด๋ฆญ
log_in = driver.find_element(By.CSS_SELECTOR, 'a[href="/signin"]')
log_in.click()
Selenium V2 - WebDriverWait ์ฌ์ฉ
์์๊ฐ ๋ณด์ผ ๋๊น์ง ๋๊ธฐํ๋ ํจ์๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
# ์์๊ฐ ๋ณด์ผ ๋๊น์ง ๋๊ธฐ
def wait_and_find(driver, by, locator:str, *, timeout:int=10):
# timeout ๋์ ์์๊ฐ ๋ณด์ผ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
return WebDriverWait(driver, timeout).until(
EC.visibility_of_element_located((by, locator))
)
def test_login():
# ํ๋กฌ ์คํ ์ด ์ ์
driver = webdriver.Chrome()
driver.get("https://store.frommyarti.com/arti")
# ๋๋ณด๊ธฐ ๋ฉ๋ด ํด๋ฆญ
menu = wait_and_find(driver,By.CSS_SELECTOR, 'img[alt="Main Menu"]')
menu.click()
# "๋ก๊ทธ์ธ ํ ์ด์ฉํด์ฃผ์ธ์." ๋ฌธ๊ตฌ ํด๋ฆญ
log_in = wait_and_find(driver,By.CSS_SELECTOR, 'a[href="/signin"]')
log_in.click()
Selenium V3 - ์์ ํ ๋๊ธฐ ์ฒ๋ฆฌ
์กด์ฌ โ ๊ฐ์์ฑ โ ํด๋ฆญ ๊ฐ๋ฅ ์ฌ๋ถ๊น์ง ๋ชจ๋ ์ฒดํฌํฉ๋๋ค. ์์ ์ ์ด์ง๋ง ์ฌ์ฉํ๋ ๋ชจ๋ ์ก์
์ ๋ํด ๋ง๋ค๊ณ ์ ์ง๋ณด์ํ๊ธฐ ํ๋ญ๋๋ค.
# ์์๊ฐ ๋ณด์ผ ๋๊น์ง ๋๊ธฐ
def safe_click(driver, locator, *, timeout:int=10):
wait_driver = WebDriverWait(driver, timeout)
# timeout ๋์ ์์๊ฐ ์กด์ฌํ๋์ง ๊ธฐ๋ค๋ฆผ
wait_driver.until(EC.presence_of_element_located(locator))
# timeout ๋์ ์์๊ฐ ๋ณด์ผ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
wait_driver.until(EC.visibility_of_element_located(locator))
# timeout ๋์ ์์๊ฐ ํด๋ฆญ ๊ฐ๋ฅํ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
element = wait_driver.until(EC.element_to_be_clickable(locator))
element.click()
def test_login():
# ํ๋กฌ ์คํ ์ด ์ ์
driver = webdriver.Chrome()
driver.get("https://store.frommyarti.com/arti")
# ๋๋ณด๊ธฐ ๋ฉ๋ด ํด๋ฆญ
safe_click(driver,(By.CSS_SELECTOR, 'img[alt="Main Menu"]'))
# "๋ก๊ทธ์ธ ํ ์ด์ฉํด์ฃผ์ธ์." ๋ฌธ๊ตฌ ํด๋ฆญ
safe_click(driver,(By.CSS_SELECTOR, 'a[href="/signin"]'))
Playwright - ์๋ ๋๊ธฐ ๋ด์ฅ
๋ณ๋์ ์ ํธ ํจ์ ์์ด๋ ์๋์ผ๋ก ๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋ฉ๋๋ค.
def test_login(page: Page):
# ํ๋กฌ ์คํ ์ด ์ ์
page.goto("https://store.frommyarti.com/arti")
# ๋๋ณด๊ธฐ ๋ฉ๋ด ํด๋ฆญ
menu=page.locator('img[alt="Main Menu"]')
menu.click()
# "๋ก๊ทธ์ธ ํ ์ด์ฉํด์ฃผ์ธ์." ๋ฌธ๊ตฌ ํด๋ฆญ
log_in=page.locator('a[href="/signin"]')
log_in.click()
Selenium๋ V3์ฒ๋ผ ์ ํธ ํจ์๋ฅผ ๋ง๋ค๋ฉด ์์ ์ ์ธ ํ
์คํธ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
๋ค๋ง click ์ธ์๋ fill, hover, select ๋ฑ ๋ชจ๋ ์ก์
์ ๋ํด ์ด๋ฐ ํจ์๋ฅผ ๋ง๋ค๊ณ ์ ์ง๋ณด์ํ๋ ค๋ฉด ๋ง์ ์๊ฐ๊ณผ ๋
ธ๋ ฅ์ด ํ์ํฉ๋๋ค.
ํ์ง๋ง Playwright๋ ์ด๋ฐ ์ฒ๋ฆฌ๊ฐ ๊ธฐ๋ณธ์ผ๋ก ๋ด์ฅ๋์ด ์์ด์, ๋ณ๋ ์ฝ๋ ์์ด๋ ๋์ผํ ์์ ์ฑ์ ์ป์ ์ ์์ต๋๋ค.
๊ฐ ์ก์ ๋ณ๋ก ์ด๋ค ํญ๋ชฉ์ ๊ฒ์ฌํ๋์ง ์์ธํ ๋ด์ฉ์ Playwright Actionability ๋ฌธ์๋ฅผ ์ฐธ๊ณ ๋ถํ๋๋ฆฝ๋๋ค.
3. Selenium๊ณผ Playwright์ ์คํ ์๋ ๋น๊ต
์๋๋ Playwright ๊ณต์ ํํ์ด์ง์ ์ ์ํด์ ํ์ด์ง ํ์ดํ์ โPlaywrightโ๊ฐ ํฌํจ๋์ด ์๋์ง ํ์ธํ๋ ๋์ผํ ํ ์คํธ์ ๋๋ค.
Selenium - 1.14์ด
"""Selenium"""
def test_has_title():
driver = webdriver.Chrome()
driver.get("https://playwright.dev/")
assert re.search(r"Playwright", driver.title)
# test_example.py::test_has_title PASSED [100%] Selenium
# ============= 1 passed in 1.14s ==============
Playwright - 0.73์ด
"""Playwright"""
def test_has_title(page: Page):
page.goto("https://playwright.dev/")
expect(page).to_have_title(re.compile("Playwright"))
# test_example.py::test_has_title[chromium] PASSED [100%] Playwright
# ============= 1 passed in 0.73s ==============
๊ฐ์ ํ
์คํธ์ธ๋ฐ Playwright๊ฐ ์ฝ 36% ๋ ๋น ๋ฆ
๋๋ค. ํ
์คํธ ์ผ์ด์ค๊ฐ ๋ง์์ง์๋ก ์ด ์ฐจ์ด๋ ๋ ์ปค์ง๋๋ค.
(์คํ ํ๊ฒฝ/๋คํธ์ํฌ์ ๋ฐ๋ผ ์์น๋ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.)
Playwright ์ฌ์ฉ ์ ์ฐธ๊ณ ์ฌํญ
Playwright๋ ๋ธ๋ผ์ฐ์ ์กฐ์ ์ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ฑํด์ฃผ๋ codegen ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
๋ค๋ง codegen์ ์ ๊ทผ์ฑ ํธ๋ฆฌ ๊ธฐ๋ฐ์ผ๋ก getByRole, getByText ๊ฐ์ ๋ก์ผ์ดํฐ๋ฅผ ์์ฑํด์ฃผ๋๋ฐ,
์ด ๋ฐฉ์์ UI ํ
์คํธ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ํ
์คํธ๊ฐ ๊นจ์ง ์ ์์ต๋๋ค.
# "๋ก๊ทธ์ธ" โ "Sign In" ๋ณ๊ฒฝ ์ ์คํจ
page.get_by_role("button", name="๋ก๊ทธ์ธ")
# ๋ ์์ ์ ์ธ ๋ฐฉ์ - id๋ data-testid ์ฌ์ฉ
page.locator('#login-btn')
page.get_by_test_id("login-button")
๋ฐ๋ผ์ codegen ์ฌ์ฉ ์ ์๋ฆฝํ์ Locator ์ ๋ต์ ๋ง๊ฒ ์์ ํด์ ์ฌ์ฉํ์๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
Playwright Locators ๋ฌธ์์์๋ test id ๋ฐฉ์์ด ๊ฐ์ฅ ์์ ์ ์ด๋ผ๊ณ ๋ช ์๋์ด ์์ต๋๋ค.
โTesting by test ids is the most resilient way of testing as even if your text or role of the attribute changes, the test will still pass.โ
๋ง๋ฌด๋ฆฌํ๋ฉฐ
| Selenium | Playwright | |
|---|---|---|
| ๋ธ๋ผ์ฐ์ ์ ์ด | WebDriver (HTTP) | WebSocket (์๋ฐฉํฅ) |
| ๋๊ธฐ ์ฒ๋ฆฌ | ์ง์ ๊ตฌํ ํ์ | ๊ธฐ๋ณธ ๋ด์ฅ |
| ์คํ ์๋ | 1.14์ด | 0.73์ด (36% ๋น ๋ฆ) |
Selenium์ด ๋์ ๋๊ตฌ๋ ์๋๋๋ค. ๋ ํผ๋ฐ์ค๋ ๋ง๊ณ , IE ๊ฐ์ ๋ ๊ฑฐ์ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํด์ผ ํ๋ค๋ฉด ์ฌ์ ํ ์ข์ ์ ํ์
๋๋ค.
ํ์ง๋ง ํธํ๊ณ ์์ ์ ์ธ ํ
์คํธ๊ฐ ํ์ํ๋ค๋ฉด Playwright๋ฅผ ์ถ์ฒ๋๋ฆฝ๋๋ค!
์ฝ์ด์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค.