
๐ฏ Selenium์ ํตํด E2E ํ ์คํธ ํ๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํฉ๋๋ค.
Selenium์ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์๋์ผ๋ก ์ ์ดํ๊ณ ํ
์คํธํ ์ ์๊ฒ ํด์ฃผ๋ ๋ธ๋ผ์ฐ์ ์๋ํ ๋๊ตฌ์
๋๋ค. ๋ค์ํ ๋ธ๋ผ์ฐ์ ์ ์ธ์ด๋ฅผ ์ง์ํ๋ฉฐ, ์ค์ ์ฌ์ฉ์์ ํ๋์ ํ๋ด ๋ด๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
Selenium WebDriver
Selenium IDE
Selenium Grid
์ถ์ฒ: selenium์ฌ์ฉ ์ด๋ฏธ์ง: selenium/standalone-chrome ( Apple Silicon(M1/M2 ๋ฑ)์์๋ seleniarm/standalone-chromium ์ฌ์ฉ )
ํฌํธ: 4444 โ Selenium Web UI + WebDriver ์ ๊ทผ ํฌํธ
์คํ ๋ช ๋ น์ด
docker run -d --rm -p 4444:4444 \
-v /dev/shm:/dev/shm \
selenium/standalone-chrome
http://localhost:4444โ ์ค์ต์์๋ Grid(hub/node ๋ถ๋ฆฌ)๋ ์ฌ์ฉํ์ง ์๊ณ , standalone ์ด๋ฏธ์ง๋ก ์งํํฉ๋๋ค.
first_test.py)from selenium import webdriver
import time
options = webdriver.ChromeOptions()
options.add_argument('--ignore-ssl-errors=yes')
options.add_argument('--ignore-certificate-errors')
driver = webdriver.Remote(
command_executor='http://localhost:4444/wd/hub',
options=options
)
driver.maximize_window()
time.sleep(10) # ํ์ธ์ฉ ์ง์ฐ
driver.get("https://notes.prgms-fullcycle.com") # ํ์ด์ง ์ด๊ธฐ
time.sleep(10)
driver.find_element("link text", "๋ฌด๋ฃ๋ก ์์ํ๊ธฐ").click() # ๋ฒํผ ํด๋ฆญ
time.sleep(10)
s = input("Done: ") # ์ฌ์ฉ์๊ฐ Enter ๋๋ฅผ ๋๊น์ง ๋๊ธฐ
driver.close()
driver.quit()
print("Test Execution Successfully Completed!")
ํ๊ณ: ๋ธ๋ผ์ฐ์ ์๋ํ ์์ฐ์ผ ๋ฟ, ํ ์คํธ ๊ฒฐ๊ณผ ๊ฒ์ฆ(assertion)์ด ์์
ํน์ ์์๊ฐ ์กด์ฌํ๋์ง ํ์ธ (assert)
ํ์ด์ง ์ด๋ ๊ฒฐ๊ณผ ๊ฒ์ฆ
์ค๋ฅ ๋ฐ์ ์ ์ฒ๋ฆฌ ๋ฑ ํ ์คํธ ๊ฒ์ฆ ๋ก์ง ์ถ๊ฐ ํ์
์ฌ์ฉ์์ ๊ด์ ์์ ์์คํ ์ ์ฒด๋ฅผ ์ ๋ ฅ โ ์ถ๋ ฅ๊น์ง ๊ฒ์ฆํ๋ ํ ์คํธ ๋ฐฉ์
์์คํ ๋ด๋ถ ๋ก์ง์ ๋ณด์ง ์์ (Black-box ํ ์คํธ)
์ค์ ์ฌ์ฉ์์ฒ๋ผ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํจ
๋จ์ ํ ์คํธ๋ก๋ ํ์ธํ ์ ์๋ ํ๋ฆ์ ๊ฒ์ฆ ๊ฐ๋ฅ
Selenium standalone (Chrome)์ ๋ก์ปฌ Kubernetes ํด๋ฌ์คํฐ์ ์ค์น
Selenium IDE๋ฅผ ํตํด ํ ์คํธ ์๋๋ฆฌ์ค๋ฅผ ๋ นํ โ ์ฝ๋๋ก ๋ณํ
๋ณํ๋ ์ฝ๋๋ฅผ ๋ณด์ํ์ฌ ์๋ ์คํ ๊ฐ๋ฅํ ํ ์คํธ ์คํฌ๋ฆฝํธ ํ๋ณด
ํฅํ CI/CD ํ์ดํ๋ผ์ธ์์ ์คํ ๊ฐ๋ฅํ ํ ์คํธ ํ๊ฒฝ ๊ตฌ์ฑ
โ ๏ธ 2024๋ ๊ธฐ์ค, Chrome ํ์ฅ ํ๋ก๊ทธ๋จ ์ ์ฑ (Manifest V3) ๋ณ๊ฒฝ์ผ๋ก ์ธํด Selenium IDE๋ ๋ ์ด์ Chrome์์ ์ค์นํ ์ ์๊ณ , ๊ณต์์ ์ผ๋ก๋ ์ง์๋์ง ์์ต๋๋ค.
Selenium IDE์ ์ค์นREC ๋ฒํผ ํด๋ฆญ โ Base URL ์
๋ ฅ
์๋์ผ๋ก ์ด๋ฆฌ๋ ํ์ด์ง์์ ์๋๋ฆฌ์ค ์ํ
"๋ฌด๋ฃ๋ก ์์ํ๊ธฐ" ํด๋ฆญ
์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ์ ๋ ฅ
๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
๋ก๊ทธ์์ ๋ฒํผ ํด๋ฆญ
.side ํ์ฅ์๋ฅผ ๊ฐ์ง ํ์ผ(simple_test.side)๋ก ์ ์ฅ๋จ. ์ด ํ์ผ์ ๋์ค์ ๋ค์ ๋ถ๋ฌ์ ์ฌ์ํ๊ฑฐ๋ ์ฝ๋๋ก ๋ณํ ๊ฐ๋ฅ
Untitled๋ก ์์ฑ๋ ํ์ผ์ ์ฐํด๋ฆญํ์ฌ Export๋ฅผ ๋๋ฅด๊ณ , Python pytest๋ฅผ ๋๋ฌ Export ํฉ๋๋ค.
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestUntitled():
def setup_method(self, method):
self.driver = webdriver.Chrome()
def teardown_method(self, method):
self.driver.quit()
def test_untitled(self):
self.driver.get("http://localhost:30030/")
self.driver.find_element(By.LINK_TEXT, "๋ฌด๋ฃ๋ก ์์ํ๊ธฐ").click()
self.driver.find_element(By.NAME, "email").send_keys("test@example.com")
self.driver.find_element(By.NAME, "password").send_keys("1234")
self.driver.find_element(By.ID, "login-button").click()
self.driver.implicitly_wait(2) # ๋ก๋ฉ ๋๊ธฐ
self.driver.find_element(By.CSS_SELECTOR, "#logout-button > span").click()
๐ก E2E ํ ์คํธ์์๋ ํ์ด์ง ๋ก๋ฉ ์๊ฐ์ด๋ ์์ ๋ ๋๋ง ์ง์ฐ์ผ๋ก ํ ์คํธ๊ฐ ์คํจํ ์ ์๊ธฐ ๋๋ฌธ์ Selenium์ wait ๊ธฐ๋ฅ์ ํ์ฉํด์ผ ํฉ๋๋ค.
๋๊ธฐ ์ ๋ต
- Implicit Wait: ์ ํด์ง ์๊ฐ ๋์ ์์๊ฐ ๋ํ๋ ๋๊น์ง ๋๊ธฐ
- Explicit Wait: ํน์ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๋๊น์ง ํด๋ง(polling)ํ๋ฉด์ ๊ธฐ๋ค๋ฆผ
pytest test_untitled.py
def login(self):
self.driver.find_element(By.LINK_TEXT, "๋ฌด๋ฃ๋ก ์์ํ๊ธฐ").click()
self.driver.find_element(By.NAME, "email").send_keys("test@example.com")
self.driver.find_element(By.NAME, "password").send_keys("1234")
self.driver.find_element(By.ID, "login-button").click()
def logout(self):
self.driver.find_element(By.CSS_SELECTOR, "#logout-button > span").click()
ํ ์คํธ ์์๊ณผ ์ข ๋ฃ ์์๋ ์๋์ฒ๋ผ ์ ์ฉํฉ๋๋ค.
def setup_method(self, method):
self.driver = webdriver.Chrome()
self.login()
def teardown_method(self, method):
self.logout()
self.driver.quit()
def test_noteview(self):
self.driver.get(BASE_URL + "/notes")
self.driver.implicitly_wait(10)
# ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์ ์ ๋ณด ํ์ธ
assert self.driver.find_element(By.ID, "current-user").text == "test@example.com"
# ๋
ธํธ ๋ฆฌ์คํธ ๊ฒ์ฆ
notes_list = self.driver.find_element(By.ID, "notes-list")
assert notes_list.find_element(By.CSS_SELECTOR, "li:nth-child(1) span").text == "Test (2)"
assert notes_list.find_element(By.CSS_SELECTOR, "li:nth-child(2) span").text == "Test (1)"
# ๋
ธํธ ์ ํ ํ ๋ด์ฉ ๊ฒ์ฆ
notes_list.find_element(By.XPATH, "li[last()]/a/span").click()
self.driver.implicitly_wait(2)
assert self.driver.find_element(By.CSS_SELECTOR, "article header textarea").text == "Test (1)"
assert self.driver.find_element(By.CSS_SELECTOR, "article main div div").get_attribute("innerHTML") == \
"<p>This note is for testing.</p><p>Note number: 1</p>"
def test_loginfail(self):
self.driver.get("http://localhost:30030/")
self.driver.find_element(By.LINK_TEXT, "๋ฌด๋ฃ๋ก ์์ํ๊ธฐ").click()
self.driver.find_element(By.NAME, "email").send_keys("test@example.com")
self.driver.find_element(By.NAME, "password").send_keys("1235") # ์๋ชป๋ ๋น๋ฒ
self.driver.find_element(By.ID, "login-button").click()
alert = self.driver.switch_to.alert
assert alert.text == "์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค."
alert.accept()
์ฌ์ฉ์ ๊ด์ ์ ํ๋ฆ์ ์๋ํํ ์ ์๋ค๋ ๊ฒ์ด ์ข ๋๋ผ์ด ๊ฑฐ ๊ฐ๋ค.