Selenium VS Playwright

wooyoung.choi
  • #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ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ํฌ๊ฒŒ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. playwright์™€ selenium์˜ ํด๋ฆญ ์•ก์…˜ ์•„๋ž˜๋Š” ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.

  • 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.โ€

๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

SeleniumPlaywright
๋ธŒ๋ผ์šฐ์ € ์ œ์–ดWebDriver (HTTP)WebSocket (์–‘๋ฐฉํ–ฅ)
๋Œ€๊ธฐ ์ฒ˜๋ฆฌ์ง์ ‘ ๊ตฌํ˜„ ํ•„์š”๊ธฐ๋ณธ ๋‚ด์žฅ
์‹คํ–‰ ์†๋„1.14์ดˆ0.73์ดˆ (36% ๋น ๋ฆ„)

Selenium์ด ๋‚˜์œ ๋„๊ตฌ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ๋ ˆํผ๋Ÿฐ์Šค๋„ ๋งŽ๊ณ , IE ๊ฐ™์€ ๋ ˆ๊ฑฐ์‹œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง€์›ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์—ฌ์ „ํžˆ ์ข‹์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ํŽธํ•˜๊ณ  ์•ˆ์ •์ ์ธ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด Playwright๋ฅผ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค!


์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ