Logo
Hyunsu Blog
browserTesting

๐Ÿ“†Published :Jul 29, 2021 โ€ข

๐Ÿ“†Updated :Jul 29, 2021 โ€ข

โ˜•๏ธ2min

Playwright์œผ๋กœ E2E ํ…Œ์ŠคํŠธ ์ž๋™ํ™” -1

playwright ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ผ์Šต๋‹ˆ๋‹ค. ๊ธ€์„ ์“ด ์‹œ์ ์˜ version์€ 1.13.0 / ์–ธ์–ด ๋ฐ ํ™˜๊ฒฝ์€ Node.js ์ž…๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์–ธ์–ด๋ฅผ ์ฐธ์กฐ ํ•˜์‹œ๋ ค๋ฉด https://playwright.dev/ ์—์„œ ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Playwright ๋Š” ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„ ์›Œํฌ? ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ? BDD Tool?

Playwright๋Š” Playwright Library๋ผ๊ณ ๋„ ๋ถˆ๋ฆฌ๊ธฐ๋„ ํ•˜๋Š”๋ฐ browser automation ํˆด๋กœ์จ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ๊น Playwright๋Š” ์‚ฌ์‹ค์ƒ ์šฐ๋ฆฌ์˜ ํ…Œ์ŠคํŠธ ๋˜๋Š” ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ์˜ ์—ฌ๋ถ€์— ๋Œ€ํ•ด์„œ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. (์‹คํŒจ, ์„ฑ๊ณตํ–ˆ๋Š”์ง€ ๋ชจ๋ฆ„) ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ์˜ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ report ๋˜ํ•œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Playwright์˜ ๊ธฐ๋ณธ์  ์ฒ ํ•™์— ๊ทผ๊ฑฐํ•œ E2E ํ…Œ์ŠคํŒ…์„ ์œ„ํ•œ ํˆด๋กœ์จ Playwright Test๋ผ๋Š” ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ๋ฅผ ์ง€์›ํ•˜๋ฉฐ ๋‹ค๋ฅธ ์™ธ๋ถ€์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Playwright์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ/ํ”„๋ ˆ์ž„์›Œํฌ :

์ž์„ธํ•œ ์‚ฌํ•ญ ์ฐธ์กฐ : https://playwright.dev/docs/test-runners/

ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ค์ •์„ ํ•˜๊ธฐ ์ „ ๋ช‡๊ฐ€์ง€ ์ฃผ์š” ์ปจ์…‰์„ ์•Œ๋ฉด ์ข‹๊ฒ ๋‹ค ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. (ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •์€ ๋‹ค์Œ ํฌ์ŠคํŒ…์œผ๋กœ ์˜ฌ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.)

Core concept

1.Browser

๋ธŒ๋ผ์šฐ์ €๋Š” Chromium, Firefox or WebKit ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. Playwright ์Šคํฌ๋ฆฝํŠธ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋Š” ํ—ค๋“œ๋ฆฌ์Šค(GUI ์—†์ด) ๋˜๋Š” ํ—ค๋“œ ๋ชจ๋“œ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งค ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒˆ๋กœ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์€ ๋น„์šฉ์ด ๋งŽ์ด ๋“ค๊ธฐ ๋•Œ๋ฌธ์— (>100ms), Playwright๋Š” ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์˜ browser contexts๋ฅผ ํ†ตํ•ด์„œ ํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์—…์„ ์ตœ๋Œ€ํ™” ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

const { chromium } = require('playwright') // Or 'firefox' or 'webkit'. const browser = await chromium.launch({ headless: false }) await browser.close()

2. BrowserContexts

Browser์—์„œ ์–ธ๊ธ‰ํ•˜์˜€๋“ฏ์ด, ์ธ์Šคํ„ด์Šค ์ดˆ๊ธฐํ™” ๋น„์šฉ์„ ์กฐ๊ธˆ ๋” ์ €๋ ดํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด Borwser Context ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋™์ผํ•œ ์ธ์Šคํ„ด์Šค์—์„œ ๊ฐ์ž ๋‹ค๋ฅธ ๊ฐœ๋ณ„ ์„ธ์…˜์œผ๋กœ ๋ถ„๋ฆฌ ํ•˜์—ฌ, ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜์„ ์šด์˜ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. Playwright์—์„œ๋Š” ํ…Œ์ŠคํŠธ ๊ฐ„์— ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒฉ๋ฆฌ ๋˜๋„๋ก, ๊ฐ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ณ ์œ ํ•œ ์ƒˆ ๋ธŒ๋ผ์šฐ์ € ์ปจํ…์ŠคํŠธ์—์„œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณต๋˜์–ด์ง€๋Š” default browser context๋Š” ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฆ‰์‹œ ์ƒ์„ฑ๋˜๋ฉฐ, ์ถ”๊ฐ€์ ์œผ๋กœ ๋” ํ•„์š”ํ•œ ๋ธŒ๋ผ์šฐ์ € ์ปจํ…์ŠคํŠธ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

//multiple contexts const { chromium } = require('playwright') // Create a Chromium browser instance const browser = await chromium.launch({ headless: false }) // Create two isolated browser contexts const userContext = await browser.newContext() const adminContext = await browser.newContext() context

3. Pages

Page๋ž€ ์‰ฝ๊ฒŒ ์ด์•ผ๊ธฐ ํ•˜์ž๋ฉด ์›น๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ฐ๊ฐ์˜ tab ๋˜๋Š” ํŒ์—…์„ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ, Browser context๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์›ํ•˜๋Š” url๋กœ ์ด๋™ํ•˜๊ณ  ํŽ˜์ด์ง€ ์ปจํ…์ธ ์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š”๋ฐ ์ด์šฉ๋ฉ๋‹ˆ๋‹ค.

// Create a page. const page = await context.newPage() // Navigate explicitly, similar to entering a URL in the browser. await page.goto('http://example.com') // Fill an input. await page.fill('#search', 'query') // Navigate implicitly by clicking a link. await page.click('#submit') // Expect a new url. console.log(page.url()) // Page can navigate from the script - this will be picked up by Playwright. window.location.href = 'https://example.com'

Playwright Page API

4. Selectors

ํ…Œ์ŠคํŠธํ•  ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์ž…๋‹ˆ๋‹ค.

Playwright๋Š” CSS selectors, XPath selectors, id,data-test-id์™€ ๊ฐ™์€ HTML attribute ๋ฐ text content๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Using data-test-id= selector engine await page.click('data-test-id=foo') // CSS and XPath selector engines are automatically detected await page.click('div') await page.click('//html/body/div') // Find node by text substring await page.click('text=Hello w') // Click an element with text 'Sign Up' inside of a #free-month-promo. await page.click('#free-month-promo >> text=Sign Up')

Playwright Selectors API

5. Auto-waiting

ํŽ˜์ด์ง€ ๋‚ด์˜ html์š”์†Œ์™€ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•ด์•ผํ•˜๋Š” ์ผ๋“ค ์ค‘ ์˜ˆ๋ฅผ ๋“ค์–ด ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญ์„ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ €์˜ ๋กœ๊ทธ์ธ ํ™”๋ฉด์—์„œ ๋ฒ„ํŠผ์ด DOM์—์„œ ๋‚˜ํƒ€๋‚˜๊ธฐ ์ „์— ์‹คํ–‰์ด ๋˜์–ด๋ฒ„๋ฆฌ๊ฑฐ๋‚˜ (๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ์ด ๋Š๋ ค์„œ), css ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚˜์•ผ ์ •์ง€๋œ ์ƒํƒœ์—์„œ ํด๋ฆญ ํ•  ์ˆ˜ ์žˆ๋‹ค๋˜์ง€, ๋ฒ„ํŠผ์ด ํŠน์ • ์‹œ์ ์— diabled ๋˜์–ด ์žˆ๋‹ค๋˜์ง€, ๋ฒ„ํŠผ์ด ์Šคํฌ๋กค์„ ํ•ด์„œ ํŽ˜์ด์ง€์˜ ํ•˜๋‹จ์œผ๋กœ ๋‚ด๋ ค๊ฐ€์•ผ ํ•˜๋Š” ์ƒํ™ฉ๋“ค์ด ์žˆ์„ ๋•Œ ํด๋ฆญ์— ์‹คํŒจํ•˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ์•„๋งˆ๋„ setTimeOut์„ ์ด์šฉํ•˜์—ฌ 1์ดˆ ์ •๋„์˜ ์‹œ๊ฐ„์  ์—ฌ์œ ๋ฅผ ๋‘” ๋‹ค์Œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋Š” ๋กœ์ง์„ ๋งŒ๋“ค์–ด์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•ญ์ƒ ์‹œ๊ฐ„์„ ๋ณด์žฅํ•ด์ฃผ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ธฐ์— ์‚ฌ์‹ค ํ•ด๊ฒฐํ–ˆ๋‹ค๊ณ  ๋งํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. setTimeOut์€ ๋˜ํ•œ flakiness๋ฅผ ๋” ์œ ๋ฐœํ•˜๋Š”๋ฐ ์†Œ์Šค์ฝ”๋“œ๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•˜์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ถˆ๊ทœ์น™ํ•˜๊ฒŒ ํ†ต๊ณผ ๋˜๋Š” ์‹คํŒจ ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ถ€๋ถ„์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด playwright์—์„  ๋ช‡๋ช‡ ๋ฉ”์„œ๋“œ์— ์ž๋™์œผ๋กœ ๊ธฐ๋‹ค๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

// Playwright waits for #search element to be in the DOM await page.fill('#search', 'query') // Playwright waits for element to stop animating // and accept clicks. await page.click('#search')

๋ช…์‹œ์ ์œผ๋กœ DOM์— ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€ ๋˜๋Š” ์—†์–ด์งˆ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

// Wait for #promo to become visible, for example with `visibility:visible`. await page.waitForSelector('#promo') // Wait for #search to appear in the DOM. await page.waitForSelector('#search', { state: 'attached' }) // Wait for #details to become hidden, for example with `display:none`. await page.waitForSelector('#details', { state: 'hidden' })

aoto-wait์„ ์ง€์›ํ•˜๋Š” method ๋ฆฌ์ŠคํŠธ ๋ณด๊ธฐ

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„  playwirght ์„ ์ด์šฉํ•˜์—ฌ ํ™˜๊ฒฝ์„ค์ •์— ๋Œ€ํ•ด ๊ธ€์„ ์จ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ ์ž๋ฃŒ

Hi, I'm Hyunsu ๐Ÿ‘‹

Profile Image

์•ˆ๋…•ํ•˜์„ธ์š”. ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ์ฃผํ˜„์ˆ˜์ž…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ํ’๋ถ€ํ•˜๊ณ  ๊ฐ€์น˜ ์žˆ๋Š” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ์ผ์— ๋ฟŒ๋“ฏํ•จ์„ ๋Š๋‚๋‹ˆ๋‹ค.

์˜ต์‹œ๋””์–ธ(Obsidian)์—์„œ ํ˜„์žฌ ๋ธ”๋กœ๊ทธ๋กœ ํ•˜๋‚˜์”ฉ ๊ธ€์„ ์˜ฎ๊ธฐ๋Š” ๊ณผ์ •์— ์žˆ์–ด์š”. โ˜•๏ธ ๐Ÿ‘ฉโ€๐Ÿ’ป โ›ท

Github on ViewReach Me Out