๐ ์ด๋ฐ ๋ถ๊ป ์ถ์ฒํฉ๋๋ค
- Selenium์ผ๋ก ์น ํผ ํ ์คํธ๋ฅผ ์๋ํํ๊ณ ์ถ์ ๋ถ
- ๋ก๊ทธ์ธ ํผ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๋น ์ง์์ด ํ์ธํ๊ณ ์ถ์ ๋ถ
- ๊ฒฝ๊ณ๊ฐยทํน์๋ฌธ์ยทSQL ์ธ์ ์ ๋ฑ์ ํ ์คํธ ์ผ์ด์ค ์ค๊ณ๋ฅผ ๋ฐฐ์ฐ๊ณ ์ถ์ ๋ถ
- Python์ผ๋ก E2E ํ ์คํธ๋ฅผ ๊ตฌํํด๋ณด๊ณ ์ถ์ QA ์์ง๋์ด
โ ์ด ๊ธ์ ์ฝ์ผ๋ฉด ์ ์ ์๋ ๊ฒ
- Selenium์ผ๋ก 35๊ฐ์ ํผ ์ ํจ์ฑ ๊ฒ์ฌ ํ ์คํธ๋ฅผ ์๋ ์คํํ๋ ๋ฐฉ๋ฒ
- ์ ๊ฐยท๋ฐ๊ฐยท๊ฒฝ๊ณ๊ฐยทํน์๋ฌธ์ ํ ์คํธ ์ผ์ด์ค ์ค๊ณ ํจํด
- FAIL ์ผ์ด์ค์ ์คํฌ๋ฆฐ์ท ์๋ ์ ์ฅ ๊ตฌํ ๋ฐฉ๋ฒ
- ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ CSVยทJSON ๋ฆฌํฌํธ๋ก ์ถ๋ ฅํ๋ ๋ฐฉ๋ฒ
๐จโ๐ป ์์ฑ์ ์๊ฐ
QA ์์ง๋์ด๋ก์ ์ค๋ฌด์์ Selenium์ ํ์ฉํ ์๋ํ ํ ์คํธ๋ฅผ ๋ด๋นํ๊ณ ์์ต๋๋ค. ๋ณธ ๊ธ์์ ์๊ฐํ๋ ์คํฌ๋ฆฝํธ๋ GitHub์ ๊ณต๊ฐ๋์ด ์์ผ๋ฉฐ, ์ค์ ๋ก ๋์ ํ์ธ์ด ์๋ฃ๋ ์ฝ๋๋ฅผ ๊ทธ๋๋ก ํด์คํฉ๋๋ค.
๋ก๊ทธ์ธ ํผ์ ๋๋ถ๋ถ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ฐ์ฅ ์ค์ํ ๊ธฐ๋ฅ ์ค ํ๋์ ๋๋ค. ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์ ๋๋ก ๋์ํ์ง ์์ผ๋ฉด ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ ์ ์๋ ๊ฒ์ ๋ฌผ๋ก , SQL ์ธ์ ์ ์ด๋ ๋ฌด๋จ ์ ๊ทผ ๊ฐ์ ์ฌ๊ฐํ ๋ณด์ ๋ฌธ์ ๋ก ์ด์ด์ง ์ ์์ต๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ฆด๋ฆฌ์ค ์ ์ ๋น ์ง์๋ ํ ์คํธ๋ฅผ ์๋ํํด๋๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค.
๋ก๊ทธ์ธ ํผ์ ์๋์ผ๋ก ํ ์คํธํ๋ฉด ์๊ฐ๋ณด๋ค ๊ฝค ๋ฒ๊ฑฐ๋กญ์ง ์์ผ์ ๊ฐ์? ์ ๊ฐ ๋ฌธ์ยท๋น ๋ฌธ์์ดยทSQL ์ธ์ ์ ยท์ด๋ชจ์ง๊น์ง, ํ์ธํด์ผ ํ ํญ๋ชฉ์ ๋์ด ์์ต๋๋ค. ์ด ๊ธ์์๋ Selenium์ ์ฌ์ฉํด ๋ก๊ทธ์ธ ํผ ์ ํจ์ฑ ๊ฒ์ฌ ํ ์คํธ๋ฅผ 35๊ฐ ์๋ ์คํํ๋ Python ์คํฌ๋ฆฝํธ๋ฅผ ํด์คํฉ๋๋ค. ๊ฒฐ๊ณผ๋ CSVยทJSON์ผ๋ก ์ถ๋ ฅ๋๊ณ , FAIL ์ผ์ด์ค๋ ์คํฌ๋ฆฐ์ท๋ ์๋ ์ ์ฅ๋ฉ๋๋ค.
- ํ ์คํธ ๊ด์
- ํ ์คํธ ์ผ์ด์ค ์ผ๋๏ผ์ ์ฒด 35๊ฐ๏ผ
- ์ ์ ๋ฐ ์คํ ๋ฐฉ๋ฒ
- ์คํ ๊ฒฐ๊ณผ ์ํ
- ์ฝ๋ ํด์ค๏ผํต์ฌ ํฌ์ธํธ 3๊ฐ์ง
- ๋ฆฌํฌํธ๋ฅผ CSVยทJSON์ผ๋ก ์๋ ์ถ๋ ฅํ๊ธฐ
- ๋ ๋ฐ์ ์ํค๋ ์์ด๋์ด
- 09. ์์ฃผ ๊ฒช๋ ๋ฌธ์ & ํด๊ฒฐ๋ฒ
- โ send_keys() ๋ก ํน์ ๋ฌธ์๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ ฅ๋์ง ์๋๋ค
- โก HTML5์ required ์์ฑ์ผ๋ก ๋ธ๋ผ์ฐ์ ์์ ์ฐจ๋จ๋๋ค
- โข ์ ์ฒด ์คํฌ๋ฆฐ์ท์ผ๋ก ํ์ผ ์๊ฐ ๋ฐฉ๋ํด์ก๋ค
- โฃ ๊ธฐ๋๊ฐ ์ค์ ์ค์๋ก FAIL์ด ๊ณ์ ๋์๋ค
- โค ์ค๋ฅ ๋ฉ์์ง ์ทจ๋ ํ์ด๋ฐ์ด ๋๋ฌด ๋นจ๋๋ค
- ์ ๋ฆฌ
ํ ์คํธ ๊ด์
์ด๋ฒ ์คํฌ๋ฆฝํธ์์๋ ๋ก๊ทธ์ธ ํผ์ ๋ํด ์๋์ ๊ด์ ์ ๋น ์ง์์ด ํ ์คํธํฉ๋๋ค. ์ค๋ฌด QA ํ์ฅ์์๋ ์์ฃผ ์ฌ์ฉ๋๋ ์ ์ ๊ด์ ๋ค์ด๋ฏ๋ก, ๊ทธ๋๋ก ์์ฌ ์ ํ์ ํ ์คํธ ์ค๊ณ์๋ ์์ฉํ ์ ์์ต๋๋ค.
- ๋น ์ ๋ ฅ ์ฒดํฌ๏ผ์ฌ์ฉ์ ์ด๋ฆยท๋น๋ฐ๋ฒํธ๊ฐ ๋ฏธ์ ๋ ฅ์ผ ๋ ์ค๋ฅ๊ฐ ๋ฐํ๋๋๊ฐ
- ๋ฌธ์ ์ข ๋ฅ ์ฒดํฌ๏ผ์ ๊ฐยท์ผ๋ณธ์ดยท์ด๋ชจ์ง ๋ฑ ์์ ์ธ์ ๋ฌธ์ ์ข ๋ฅ๋ฅผ ๊ฑฐ๋ถํ๋๊ฐ
- ํ์ยท๊ณต๋ฐฑ ํผ์ ์ฒดํฌ๏ผํ์์ด ์๋ชป๋ ์ ๋ ฅ(์ด๋ฉ์ผ ํ์, ๊ณต๋ฐฑ ํฌํจ)์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฑฐ๋ถํ๋๊ฐ
- ๊ฒฝ๊ณ๊ฐ ์ฒดํฌ๏ผ1์ยท255์ ๋ฑ ๊ธธ์ด์ ๋์ ์์ ์ด์์ด ๋ฐ์ํ์ง ์๋๊ฐ
- ๋ณด์ ์ฒดํฌ๏ผSQL ์ธ์ ์ ยทXSS ๋ฑ์ ๊ณต๊ฒฉ ๋ฌธ์์ด์ ๋ฌดํดํํ๋๊ฐ
- ์ ์ ๋ก๊ทธ์ธ๏ผ์ฌ๋ฐ๋ฅธ ์ธ์ฆ ์ ๋ณด๋ก ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๊ณ ๋ฆฌ๋ค์ด๋ ํธ๋๋๊ฐ
ํ ์คํธ ์ผ์ด์ค ์ผ๋๏ผ์ ์ฒด 35๊ฐ๏ผ
ํ ์คํธ ์ผ์ด์ค๋ 4๊ฐ์ ์นดํ ๊ณ ๋ฆฌ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ค๋ฌด์์ ๋์น๊ธฐ ์ฌ์ด ์ ๊ฐ ๋ฌธ์๋ ํน์๋ฌธ์ ๊ณ์ด์ ๋ํ ๊ฒ ์ปค๋ฒํ๊ณ ์์ต๋๋ค.
| ์นดํ ๊ณ ๋ฆฌ | ID | ๊ฑด์ | ์ฃผ์ ๋ด์ฉ |
|---|---|---|---|
| ์ ๊ฐ ๋ฌธ์ | Z-01ใZ-08 | 8๊ฑด | ์ ๊ฐ ์์ยท์ผ๋ณธ์ดยทํ๋ผ๊ฐ๋ยท์นดํ์นด๋ยท์ ๊ฐ ๊ธฐํธยท์ ๊ฐ ๊ณต๋ฐฑ |
| ๋ฐ๊ฐ ๋ฌธ์ | H-01ใH-07 | 7๊ฑด | ๋๋ฌธ์ยท์๋ฌธ์ยท์ซ์๋งยท๊ณต๋ฐฑ ํผ์ ยท์ด๋ฉ์ผ ํ์ |
| ๊ธธ์ด / ๊ฒฝ๊ณ๊ฐ | L-01ใL-10 | 10๊ฑด | ๋น ๋ฌธ์์ดยท1์ยท2์ยท3์ยท50์ยท255์ |
| ํน์ ๋ฌธ์ | S-01ใS-10 | 10๊ฑด | SQL ์ธ์ ์ ยทXSSยท์ด๋ชจ์งยท๊ฐํยทํญยทURL ์ธ์ฝ๋ฉ |
โ ๏ธ ์จ๊ฒจ์ง ์ฅ์น๏ผH-01๏ผ์ ์ ๋ก๊ทธ์ธ๏ผ์ expected_error: True๋ก ์๋์ ์ผ๋ก ์๋ชป๋ ๊ธฐ๋๊ฐ์ ์ค์ ํ์ต๋๋ค. ์ด๋ ๋ฐ๋ชจ์ฉ์ผ๋ก FAIL์ ๋ฐ์์์ผ ์คํฌ๋ฆฐ์ท ์ ์ฅ ๋์์ ํ์ธํ๊ธฐ ์ํ ์ฅ์น์
๋๋ค. ์ค์ ์ฌ์ฉ ์์๋ False๋ก ์์ ํ์ธ์.
์ ์ ๋ฐ ์คํ ๋ฐฉ๋ฒ
ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
์๋ ๋ ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ค์นํ๋ฉด ๋์ํฉ๋๋ค. ChromeDriver ์๋ ๊ด๋ฆฌ๋ ํ์ ์์ต๋๋ค.
pip install selenium webdriver-manager
์คํ ๋ช ๋ น์ด
# ๋ธ๋ผ์ฐ์ ๋ฅผ ํ์ํ๋ฉฐ ์คํ๏ผ๊ธฐ๋ณธ๊ฐ๏ผ
python form_validation_test.py
# ํค๋๋ฆฌ์ค ๋ชจ๋๏ผ๋ธ๋ผ์ฐ์ ๋นํ์ยทCI ํ๊ฒฝ์ฉ๏ผ
python form_validation_test.py --headless
๐ก Tip๏ผ์ฒซ ์คํ ์ webdriver-manager๊ฐ ChromeDriver๋ฅผ ์๋์ผ๋ก ๋ค์ด๋ก๋ํฉ๋๋ค. ๋ ๋ฒ์งธ ์คํ๋ถํฐ๋ ์บ์๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ๋น ๋ฅด๊ฒ ์์๋ฉ๋๋ค.
์คํ ๊ฒฐ๊ณผ ์ํ
์คํํ๋ฉด ํฐ๋ฏธ๋์ ์ค์๊ฐ์ผ๋ก ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค. FAIL ์ผ์ด์ค๋ ์์ธ๊ณผ ํจ๊ป ์คํฌ๋ฆฐ์ท ๊ฒฝ๋ก๋ ํ์๋ฉ๋๋ค.
==============================================================
Selenium Form Validation Test (headless)
Target : //the-internet.herokuapp.com/login
Cases : 35
Start : 2025-07-01 10:00:00
==============================================================
(01/35) Full-width alpha username ... โ
PASS (1823ms)
(02/35) Full-width numeric password ... โ
PASS (1654ms)
(03/35) Full-width space in username ... โ
PASS (1701ms)
...
(09/35) Valid login (correct creds) ... โ FAIL (2100ms)
โ expected_error:True / actual_error:False
๐ธ screenshots/H-01_FAIL.png
...
(35/35) URL-encoded characters ... โ
PASS (1590ms)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Test Summary
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Total : 35
โ
PASS : 34
โ FAIL : 1
โ ๏ธ ERROR : 0
Rate : 97.1%
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์ฝ๋ ํด์ค๏ผํต์ฌ ํฌ์ธํธ 3๊ฐ์ง
โ JavaScript๋ฅผ ํตํด ํผ์ ๊ฐ ์ ๋ ฅํ๊ธฐ
Selenium์ send_keys()๋ ๊ฐํ๏ผ\n๏ผ์ด๋ ํญ๏ผ\t๏ผ๊ฐ์ ํน์๋ฌธ์๋ฅผ ์ ํํ ์
๋ ฅํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. JavaScript๋ก ์ง์ ๊ฐ์ ์ค์ ํ๋ฉด ์ด๋ค ๋ฌธ์๋ ์์ ํ๊ฒ ์
๋ ฅํ ์ ์์ต๋๋ค.
arguments[0]์๋ WebElement, arguments[1]์๋ ์
๋ ฅํ ๋ฌธ์์ด์ ์ ๋ฌํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ธ๋ผ์ฐ์ ์ ํค ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ๊ฑด๋๋ฐ๊ณ DOM ์์์ ์ง์ ๊ฐ์ ์๋๋ค. ์ด๋ชจ์งยท์ ๊ฐ ๋ฌธ์ยท์ ์ด ๋ฌธ์์ฒ๋ผ send_keys๋ก๋ ๊นจ์ง๊ธฐ ์ฌ์ด ๋ฌธ์๋ ํ์คํ๊ฒ ๋์ํ๋ฏ๋ก, ํน์๋ฌธ์ ๊ณ์ด ํ
์คํธ ์ผ์ด์ค๏ผS-05 ๊ฐํยทS-06 ํญ๏ผ๋ ์ด ๋ฐฉ์ ๋๋ถ์ ์ฑ๋ฆฝํฉ๋๋ค.
driver.execute_script("arguments[0].value = arguments[1];", u_elem, tc["username"])
driver.execute_script("arguments[0].value = arguments[1];", p_elem, tc["password"])
โก HTML5 ์ ํจ์ฑ ๊ฒ์ฌ ์ฐํํ๊ธฐ
๋น ๋ฌธ์์ด ํ
์คํธ๏ผL-01ใL-03๏ผ๋ HTML5์ required ์์ฑ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ธก์์ ์ฐจ๋จ๋ฉ๋๋ค. novalidate๋ฅผ ๋ถ์ฌํ๋ฉด ์์ฒญ์ด ์๋ฒ๊น์ง ๋๋ฌํ๋ฏ๋ก ์๋ฒ ์ธก ๋์๊น์ง ํ์ธํ ์ ์์ต๋๋ค.
์ ํจ์ฑ ๊ฒ์ฌ ํ ์คํธ์ ๋ณธ๋ ๋ชฉ์ ์ “๋ธ๋ผ์ฐ์ ๊ฐ ์ค๋ฅ๋ฅผ ํ์ํ๋๊ฐ”๊ฐ ์๋๋ผ “์๋ฒ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ค๋ฅ๋ฅผ ๋ฐํํ๋๊ฐ”์ ๋๋ค. ํ๋ก ํธ์๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์๋์ ์ผ๋ก ๋ฌดํจํํจ์ผ๋ก์จ ๋ฐฑ์๋์ ๊ฒฌ๊ณ ์ฑ์ ์ง์ ํ ์คํธํ ์ ์์ต๋๋ค.
driver.execute_script(
"document.querySelector('form').setAttribute('novalidate','true');"
)
โข FAIL ์ผ์ด์ค๋ง ์คํฌ๋ฆฐ์ท ์ ์ฅํ๊ธฐ
๋ชจ๋ ์ผ์ด์ค์ ์คํฌ๋ฆฐ์ท์ ์ฐ์ผ๋ฉด ํ์ผ ์๊ฐ ๋ฐฉ๋ํด์ง๋๋ค. FAIL ์์๋ง ์ ์ฅํ๋ ์ค๊ณ๋ก ๋์ค์ ์์ธ ํ์ธ์ด ํจ์ฌ ์ฌ์์ง๋๋ค.
ํ์ผ๋ช
์ {ํ
์คํธID}_FAIL.png ํ์์ด๋ฏ๋ก ํ์ผ๋ช
๋ง ๋ด๋ ์ด๋ ํ
์คํธ ์ผ์ด์ค๊ฐ ์คํจํ๋์ง ๋ฐ๋ก ์ ์ ์์ต๋๋ค. GitHub Actions ๊ฐ์ CI/CD ํ๊ฒฝ์์ ํ
์คํธ๋ฅผ ์คํํ ๋๋ ์ด ์คํฌ๋ฆฐ์ท์ ์ํฐํฉํธ๋ก ์ ์ฅํด๋๋ฉด ๋๋ฒ๊น
ํจ์จ์ด ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
if r.status == "FAIL":
os.makedirs("screenshots", exist_ok=True)
ss = f"screenshots/{tc['id']}_FAIL.png"
driver.save_screenshot(ss)
r.screenshot = ss
๐ก ์ค๋ฌด Tip๏ผPage Object Model๏ผPOM๏ผ์ ๋ํด
์ค๋ฌด์์๋ Page Object Model๏ผPOM๏ผ์ ์ฌ์ฉํด ํ
์คํธ ์ฝ๋์ ํ์ด์ง ์กฐ์์ ๋ถ๋ฆฌํ๋ ์ค๊ณ๊ฐ ๊ถ์ฅ๋ฉ๋๋ค. ์ด ์คํฌ๋ฆฝํธ๋ ๊ฐ๋จํ ๊ตฌ์ฑ์ด์ง๋ง, ํ
์คํธ ์ผ์ด์ค๊ฐ ๋์ด๋๋ฉด LoginPage ํด๋์ค์ ํผ ์กฐ์์ ๋ชจ์๋๋ ๊ฒ์ผ๋ก ์ ์ง๋ณด์์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ด ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
๋ฆฌํฌํธ๋ฅผ CSVยทJSON์ผ๋ก ์๋ ์ถ๋ ฅํ๊ธฐ
ํ ์คํธ ์๋ฃ ํ ํ์์คํฌํ๊ฐ ๋ถ์ ๋ฆฌํฌํธ ํ์ผ์ด ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค.
๐ report_20250701_100000.csv
๐ report_20250701_100000.json
| ์ถ๋ ฅ ํ์ | ์ฃผ์ ์ฉ๋ |
|---|---|
| CSV | Excel์์ ์ด์ด ๋ฆฌ๋ทฐยทํํฐ๋งยทํ ๊ณต์ ยท์๋น๋์ค ์ ์ถ |
| JSON | GitHub Actions ์ฐ๋ยทSlack ์๋ฆผยทAllure ๋ฆฌํฌํธ ๋ฐ์ดํฐ ์ฐ๊ณ |
๋ ๋ฐ์ ์ํค๋ ์์ด๋์ด
โก ๋ณ๋ ฌ ์คํ์ผ๋ก ๊ณ ์ํ
concurrent.futures๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ๋ฌ ๊ฐ ๊ธฐ๋ํ๊ณ ํ
์คํธ๋ฅผ ๋ณ๋ ฌ ์คํํ๋ฉด ์คํ ์๊ฐ์ ๋ํญ ๋จ์ถํ ์ ์์ต๋๋ค.
๐ ์ฌ์๋ ์ฒ๋ฆฌ ์ถ๊ฐ
๋คํธ์ํฌ๊ฐ ๋ถ์์ ํ ํ๊ฒฝ์์๋ ์์ ์ ์ผ๋ก ๋์ํ๋๋ก ERROR ์ผ์ด์ค์ ์๋ ์ฌ์๋ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๐ ๋ฆฌํฌํธ ์๊ฐํ
JSON ๋ฆฌํฌํธ๋ฅผ ๋ฐํ์ผ๋ก Allure๋ HTML ๋ฆฌํฌํธ๋ฅผ ์์ฑํ๋ฉด ํ๊ณผ์ ๊ณต์ ๊ฐ ํจ์ฌ ์ํํด์ง๋๋ค.
๐ค CI/CD ์ฐ๋
GitHub Actions๋ Jenkins์ ํตํฉํด PR๋ง๋ค ์๋์ผ๋ก ํ ์คํธ๋ฅผ ์คํํ๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
๐ก ํ์ฉ ์๋๋ฆฌ์ค ๋ฐ ํ์ฅ ์์
- ๋งค ๋ฆด๋ฆฌ์ค ์ ํ๊ท ํ ์คํธ์ ์ผํ์ผ๋ก ์คํ
- ์ ๊ธฐ๋ฅ ๋ฆด๋ฆฌ์ค ์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ์ ์ ๋์ ํ์ธ
- ์คํ ์ด์ง ํ๊ฒฝ์์์ ๋ณด์ ํ์ธ๏ผSQLiยทXSS๏ผ
- ํ์๊ฐ์ ยท๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ํผ์ผ๋ก ์์ฉ ํ์ฅ
- ํ ์คํธ ์ผ์ด์ค๋ฅผ CSV์์ ์ฝ์ด์ค๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํด ๋ ธ์ฝ๋ ๊ด๋ฆฌ
- pytest-html์ด๋ Allure์ ์กฐํฉํด ๋ฆฌํฌํธ ์๊ฐํ
09. ์์ฃผ ๊ฒช๋ ๋ฌธ์ & ํด๊ฒฐ๋ฒ
๊ตฌํ ์ค ์ค์ ๋ก ๊ฒช์๋ ๋ฌธ์ ๋ค์ ์ ๋ฆฌํ์ต๋๋ค. ๊ฐ์ ๊ณณ์์ ๋งํ๋ ๋ถ๋ค๊ป ๋์์ด ๋๋ฉด ์ข๊ฒ ์ต๋๋ค.
โ send_keys() ๋ก ํน์ ๋ฌธ์๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ ฅ๋์ง ์๋๋ค
์ค๋ฐ๊ฟ(\n)ยทํญ(\t)ยท์ด๋ชจ์ง ๋ฑ์ ํน์ ๋ฌธ์๋ฅผ send_keys() ๋ก ์
๋ ฅํ๋ฉด ๋ฌธ์๊ฐ ๊นจ์ง๊ฑฐ๋ ์๋์น ์์ ๋์์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค. ํน์ ๋ฌธ์๋ JavaScript๋ฅผ ํตํด DOM์ ์ง์ ๊ฐ์ ์ฐ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ์ต๋๋ค.
# โ send_keys()๋ ํน์ ๋ฌธ์๊ฐ ๊นจ์ง ์ ์์
username_field.send_keys("test\nuser")
# โ
JS๋ฅผ ํตํด ์ง์ ๊ฐ์ ์ค์
driver.execute_script("arguments[0].value = arguments[1];", username_field, "test\nuser")
๐ก ํฌ์ธํธ๏ผ arguments[0] ์ WebElement, arguments[1] ์ ์
๋ ฅํ ๋ฌธ์์ด์ ์ ๋ฌํ๋ฉด ๋ธ๋ผ์ฐ์ ์ ํค ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ๊ฑด๋๋ฐ๊ณ ํ์คํ๊ฒ ๊ฐ์ ์ธ ์ ์์ต๋๋ค.
โก HTML5์ required ์์ฑ์ผ๋ก ๋ธ๋ผ์ฐ์ ์์ ์ฐจ๋จ๋๋ค
๋น ๋ฌธ์์ด ํ
์คํธ๋ฅผ ์คํํ๋ ค ํ๋๋ ํผ์ required ์์ฑ์ผ๋ก ์ธํด ๋ธ๋ผ์ฐ์ ์ธก์์ ์์ฒญ์ด ์ฐจ๋จ๋์ด ์๋ฒ ์ธก ๋์์ ํ์ธํ ์ ์์์ต๋๋ค.
# โ required๊ฐ ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์ฐจ๋จํ์ฌ ์๋ฒ์ ์ ๋ฌ๋์ง ์์
submit_button.click()
# โ
novalidate๋ฅผ ์ถ๊ฐํ์ฌ HTML5 ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ฐํ
driver.execute_script(
"document.querySelector('form').setAttribute('novalidate', 'true');"
)
submit_button.click()
๐ก ํฌ์ธํธ๏ผ ใ๋ธ๋ผ์ฐ์ ๊ฐ ์ค๋ฅ๋ฅผ ํ์ํ์ง ์๋๋ค๏ผํ ์คํธ ํต๊ณผใ๊ฐ ์๋๋ผ, ใ์๋ฒ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ค๋ฅ๋ฅผ ๋ฐํํ๋๊ฐใ๊น์ง ํ์ธํ๋ ๊ฒ์ด ์ ํจ์ฑ ๊ฒ์ฌ ํ ์คํธ์ ๋ณธ๋ ๋ชฉ์ ์ ๋๋ค.
โข ์ ์ฒด ์คํฌ๋ฆฐ์ท์ผ๋ก ํ์ผ ์๊ฐ ๋ฐฉ๋ํด์ก๋ค
์ฒ์์๋ ์ ์ฒด 35๊ฑด์ ๋ํด ๋งค๋ฒ ์คํฌ๋ฆฐ์ท์ ์ฐ์๋๋ ํ์ผ์ด ๋๋์ผ๋ก ์์ฑ๋์ด ์ด๋ค ๊ฒ์ด ์ค์ํ์ง ์ ์ ์๊ฒ ๋์ต๋๋ค. FAIL ์ผ์ด์ค๋ง ์ ์ฅํ๋ ์ค๊ณ๋ก ๋ฐ๊พธ๋ ๊ด๋ฆฌํ๊ธฐ ์ฌ์์ก์ต๋๋ค.
# โ ์ ์ฒด ์ ์ฅํ๋ฉด ํ์ผ ์๊ฐ ๋ฐฉ๋ํด์ง
driver.save_screenshot(f"screenshots/{tc['id']}.png")
# โ
FAIL์ผ ๋๋ง ์ ์ฅ
if result.status == "FAIL":
os.makedirs("screenshots", exist_ok=True)
driver.save_screenshot(f"screenshots/{tc['id']}_FAIL.png")
๐ก ํฌ์ธํธ๏ผ ํ์ผ๋ช
์ {ํ
์คํธID}_FAIL.png ํ์์ผ๋ก ํด๋๋ฉด ์ด๋ค ํ
์คํธ ์ผ์ด์ค์์ ์คํจํ๋์ง ํ์ผ๋ช
๋ง ๋ด๋ ๋ฐ๋ก ์ ์ ์์ต๋๋ค.
โฃ ๊ธฐ๋๊ฐ ์ค์ ์ค์๋ก FAIL์ด ๊ณ์ ๋์๋ค
์ ์ ๋ก๊ทธ์ธ ํ
์คํธ ์ผ์ด์ค(H-01)์ expected_error: True ๋ก ์๋ชป ์ค์ ํด์, ์ฌ๋ฐ๋ฅด๊ฒ ๋ก๊ทธ์ธ๋๊ณ ์๋๋ฐ๋ ๊ณ์ FAIL๋ก ํ์ ๋์์ต๋๋ค. ๊ธฐ๋๊ฐ ์ค์ ์ค์๋ ๋๋ฒ๊น
์ ์์์ฑ๊ธฐ ์ด๋ ค์ฐ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.
# โ ๊ธฐ๋๊ฐ์ด ์๋ชป๋๋ฉด ์ ์ ๋์์์๋ FAIL์ด ๋จ
{"id": "H-01", "username": "tomsmith", "password": "SuperSecretPassword!", "expected_error": True}
# โ
์ ์ ๋ก๊ทธ์ธ์ expected_error: False๊ฐ ๋ง์
{"id": "H-01", "username": "tomsmith", "password": "SuperSecretPassword!", "expected_error": False}
โ ๏ธ ์ฃผ์๏ผ ํ ์คํธ๊ฐ FAILํ์ ๋, ๋จผ์ ใ์ฝ๋ ๋ฒ๊ทธ์ธ์งใใ๊ธฐ๋๊ฐ ์ค์ ์ค์์ธ์งใ๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
โค ์ค๋ฅ ๋ฉ์์ง ์ทจ๋ ํ์ด๋ฐ์ด ๋๋ฌด ๋นจ๋๋ค
ํผ ์ ์ถ ์งํ์ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ทจ๋ํ๋ ค ํ๋๋ ์์ง ํ๋ฉด์ด ์ ๋ฐ์ดํธ๋์ง ์์ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ฐพ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค. ์ ์ถ ํ ๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ํ์ํ์ต๋๋ค.
# โ ์ ์ถ ์งํ์ ์ค๋ฅ๋ฅผ ์ทจ๋ํ๋ ค ํ๋ฉด ์ฐพ์ง ๋ชปํ ์ ์์
submit_button.click()
error = driver.find_element(By.ID, "flash")
# โ
WebDriverWait์ผ๋ก ์์๊ฐ ํ์๋ ๋๊น์ง ๋๊ธฐ
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
submit_button.click()
error = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "flash"))
)
๐ก ํฌ์ธํธ๏ผ time.sleep() ์ผ๋ก ๊ณ ์ ๋๊ธฐํ๋ ๊ฒ๋ณด๋ค WebDriverWait ์ผ๋ก ์กฐ๊ฑด ๋๊ธฐํ๋ ํธ์ด ๋ ๋น ๋ฅด๊ณ ์์ ์ ์
๋๋ค.
์ ๋ฆฌ
์ด ๊ธ์์๋ Selenium๊ณผ Python์ผ๋ก ๋ก๊ทธ์ธ ํผ ์ ํจ์ฑ ๊ฒ์ฌ ํ ์คํธ๋ฅผ ์๋ํํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ์ต๋๋ค.
| ํฌ์ธํธ | ๋ด์ฉ |
|---|---|
| ํ ์คํธ ์ผ์ด์ค | ์ ๊ฐยท๋ฐ๊ฐยท๊ฒฝ๊ณ๊ฐยทํน์๋ฌธ์ 4๊ฐ์ง ์นดํ ๊ณ ๋ฆฌ ์ด 35๊ฑด |
| ์ ๋ ฅ ๋ฐฉ๋ฒ | JS๋ฅผ ํตํด ์ด๋ค ํน์๋ฌธ์๋ ํ์คํ๊ณ ์์ ํ๊ฒ ์ ๋ ฅ |
| ์คํฌ๋ฆฐ์ท | FAIL ์ผ์ด์ค๋ง ์๋ ์ ์ฅ๏ผ๊ฑด์๋ฅผ ์ค์ฌ ๊ด๋ฆฌ ์ฉ์ด๏ผ |
| ๋ฆฌํฌํธ ์ถ๋ ฅ | CSVยทJSON ํ์์ผ๋ก ํ์์คํฌํ ํฌํจ ์๋ ์์ฑ |
| ์คํ ๋ชจ๋ | --headless ์ต์
์ผ๋ก CI/CD ํ๊ฒฝ์๋ ๋์ |
์ค๋ฌด์์๋ ์ด๋ฌํ ํ ์คํธ๋ฅผ CI/CD ํ์ดํ๋ผ์ธ์ ํตํฉํ๊ณ ํ ๋ฆฌํ์คํธ๋ง๋ค ์๋ ์คํํจ์ผ๋ก์จ ํ์ง์ ์ง์์ ์ผ๋ก ๊ฒ์ฆํฉ๋๋ค. Selenium ํ ์คํธ๋ ํ๊ท ํ ์คํธ์๋ ๋๋ฆฌ ํ์ฉ๋๋ฉฐ, ๋ฐฐํฌ๋ง๋ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ด ์ ์์ ์ผ๋ก ๋์ํ๋์ง ์๋์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค.
์์ค ์ฝ๋๋ GitHub์ ๊ณต๊ฐ๋์ด ์์ผ๋ ๊ผญ ์ง์ ์คํํด๋ณด์ธ์๏ผํผ์ ์ข ๋ฅ๋ฅผ ๋ฐ๊พธ๋ ๊ฒ๋ง์ผ๋ก ๋ค์ํ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ ์คํธ์ ์์ฉํ ์ ์์ต๋๋ค. ๊ถ๊ธํ ์ ์ ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์ ๐

