์์ฆ ๋ฆด๋ฆฌ์ฆ๊ฐ ๋๋๋ฉด ๋ค์ ๋ฆด๋ฆฌ์ฆ ์ค๋น๊น์ง ์ ์ ์๊ธด ํ์ ์๊ฐ์, ํ์ฌ ์๋น์ค ์๋ํ ํ๋ก์ ํธ๋ฅผ ์งํํ๊ณ ์๋ค. ํ์ฌ ์คํฐ๋์์๋ JavaScript๋ฅผ ๊ณต๋ถํ๊ณ , ๊ฐ์ธ์ ์ผ๋ก๋ TypeScript์ Playwright๋ฅผ ํจ๊ป ํ๊ณ ๋ค๋ฉฐ ‘์๋ํ ํ๋ก์ธ์ค’๋ผ๋ ์์ ์๋ก์ด ์์ญ์ ํผ์์ ๋ถ๋ชํ๋ฉฐ ๊ตฌ์ถํ๊ณ ์๋ค.
QA Manager๋ก์ 'ํ ์คํธ ์๋ํ ๊ตฌ์ถ'์ด๋ผ๋ ํฐ ๊ณผ์ ๋ฅผ ๋ฐ์์ง๋ง, ์ค์ ์๋น์ค ๊ธฐ๋ฐ์์์ ๊ฒฝํ์ ๊ฑฐ์ ์ ๋ฌดํ๋ค. ๊ทธ๋์ ๋ชจ๋ ๊ฒ์ด ์๋ก์ ๊ณ , ๊ทธ๋์ ๋ ๊ถ๊ธํ๋ค.
๋ณธ์ธ ์ฑ๊ฒฉ์ ํญ์ '์?'๋ผ๋ ์ง๋ฌธ์ ๋์์์ด ๋์ง๊ธฐ์, Playwright ๊ตฌ์กฐ๋ฅผ ๋ดค์ ๋๋ ๋ง์ฐฌ๊ฐ์ง์๋ค. '์ด๊ฒ ์ ์ด๋ ๊ฒ ๋์ํ์ง?', '์ ์ด๋ฐ ๋ฌธ๋ฒ์ ์ฌ์ฉํ์ง?', '์ ๊ฐ์ฒด๋ ์ด๋์ ์์ฑ๋๋ ๊ฑฐ์ง?' ๋ผ๋ ์ง๋ฌธ๋ค์ ๋์์์ด ๋์ง๋ฉฐ ํ๊ณ ๋ค์๋ค. ํ๊ณ ๋ค๋ค ๋ณด๋ ์์ฐ์ค๋ฝ๊ฒ ํ์ ์คํฌ๋ฆฝํธ์ ๋ฌธ๋ฒ๋ ์๊ฒ ๋์๊ณ , ์ฌํด ์ด ๋ถํธ์บ ํ์์ ๋ฐฐ์ ๋ Pytest + Selenium๊ณผ์ ์ฐจ์ด์ ๋ ์์ฐ์ค๋ฝ๊ฒ ์ดํดํ ์ ์์๋ค. ์ด์ ๋ํ ๋ถ๋ถ์ ๋ค์ ํฌ์คํ ์ ํตํด ์์ธํ ๋ค๋ฃฐ ์์ ์ด๋ค.
๊ทธ๋์ ์ค๋ ๊ธ์ Playwright์ ๊ตฌ์กฐ๋ฅผ ํ๋์ฉ ๋ฏ์ด๋ณด๋ฉฐ ‘์ ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ์ฐ๋์ง’ ์ดํดํ ๊ณผ์ ์ ์ ๋ฆฌํด๋ณด๋ ค ํ๋ค. ๋๊ตฐ๊ฐ๋ "์ด๊ฒ ์ ๊ถ๊ธํ์ง?"๋ผ๊ณ ์๊ฐํ ์๋ ์์ง๋ง, ๋์๊ฒ๋ ์ ๋ง ๊ถ๊ธํ๊ณ ํ๊ณ ๋๋ ๊ณผ์ ์์ฒด๊ฐ ํฐ ๋์์ด ๋์๋ค. ํ๋ฉด์ ์ธ ์ฌ์ฉ๋ฒ๋ง ์๋ ๊ฒ๊ณผ, ๋ด๋ถ ๊ตฌ์กฐ๊น์ง ์ดํดํ๊ณ ์ฌ์ฉํ๋ ๊ฒ์ ์ฐจ์ด๋ ์ค์ ๋ก ์์ฒญ ์ปธ๊ธฐ ๋๋ฌธ์ด๋ค.
๐ ์์: ์๋ ์์ฑ๋ ์์ ์ฝ๋
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Playwright ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด ์๋์ผ๋ก ์ ๊ณต๋๋ tests/example.spec.ts ํ์ผ์ ์์ ๊ฐ๋ค.
์ฒ์ ์ด ์ฝ๋๋ฅผ ๋ดค์ ๋ ๋๋ ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ๋ค์ด ๋ ์ฌ๋๋ค.
- @playwright/test๋ ๋๋์ฒด ๋ญ์ง?
- test() ํจ์๋ ์ด๋ค ๊ตฌ์กฐ๋ฅผ ๊ฐ๋ ๊ฑฐ์ง?
- page๋ ๋ญ๊ณ , ์ ํจ์ ์ธ์์์ { page }์ฒ๋ผ ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ์ฐ๋ ๊ฑฐ์ง?
์ด ์ง๋ฌธ๋ค์ ํ๋์ฉ ๋ฐ๋ผ๊ฐ ๋ณด๊ธฐ๋ก ํ๋ค.
๐ @playwright/test
Playwright๋ ํฌ๊ฒ 2๊ฐ์ง ๋ ์ด์ด๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
- Core API
-> Browser / Page / Locator ๊ฐ์ '์๋ํ ์์ง' - Test Runner (@playwright/test)
-> test(), expect(), fixtures, ๋ณ๋ ฌ ์คํ ๋ฑ ํ ์คํธ ๋ฌ๋ ๊ธฐ๋ฅ
์ฐ๋ฆฌ๊ฐ ๋ณดํต ์ฌ์ฉํ๋ '@playwright/test'๋ Core API๊ฐ ์๋ Playwright๊ฐ ์์ฒด ์ ๊ณตํ๋ ํ ์คํธ ๋ฌ๋ ํจํค์ง๋ค.
์ฌ๊ธฐ์๋ ๋ค์ ๊ธฐ๋ฅ๋ค์ด ํจ๊ป ํฌํจ๋๋ค:
- ํ ์คํธ ์คํ๊ธฐ
- test / expect ๊ฐ์ ์ ์ญ ํจ์
- ํ ์คํธ๋ง๋ค ๋ ๋ฆฝ์ ์ธ BrowserContext ๊ด๋ฆฌ
- ํ์ฅ ๊ฐ๋ฅํ Fixture ์์คํ
- ๋ณ๋ ฌ ์คํ, ํ๋ก์ ํธ ๊ตฌ์กฐ, ์ ฐ๋์ ๋ฑ
์ฆ, 'Playwright๋ฅผ ํ ์คํธ ํ๋ ์์ํฌ์ฒ๋ผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ๋ฐ๋์ ํ์ํ ํจํค์ง'๊ฐ ๋ฐ๋ก ์ด์ ํด๋นํ๋ค.
๐ test() ํจ์์ ๊ตฌ์กฐ ๋ถํด ํ ๋น
๊ฒ๋ณด๊ธฐ์๋ ๋จ์ํ ํจ์์ฒ๋ผ ๋ณด์ด์ง๋ง, test()๋ ์ฌ์ค ์ฌ๋ฌ ํ์ ๊ณผ ๊ธฐ๋ฅ์ด ์กฐํฉ๋ ๋ณตํฉ์ ์ธ ํจ์๋ค.
ํ ์คํธ ๋ฌ๋์ ํต์ฌ ๊ธฐ๋ฅ ๋๋ถ๋ถ์ด ์ด test ๊ฐ์ฒด๋ฅผ ์ค์ฌ์ผ๋ก ๋์ํ๋ค.
์ ์๋ฅผ ๋ฐ๋ผ๊ฐ ๋ณด๋ฉด, ์๋์ ๊ฐ๋ค.
const test: TestType = (
title: string,
body: TestBody<
PlaywrightTestArgs &
PlaywrightTestOptions &
PlaywrightWorkerArgs &
PlaywrightWorkerOptions
>
) => void
์ฆ, Playwright์ test ํจ์๋ ๋จ์ผ ํ์ ๋ง ๋๊ธฐ์ง ์๊ณ ๋ค์ ๋ค ๊ฐ์ง ํ์ ์ ์ ๋ถ ํฉ์น ํ๋์ ๊ฐ์ฒด๋ฅผ ๋๊ธด๋ค.
- PlaywrightTestArgs: ํ ์คํธ๋ง๋ค ์๋ก ๋ง๋ค์ด์ง๋ ์์ (page, context ๋ฑ)
- PlaywrightWorkerArgs: ๊ฐ์ ์์ปค์ ์ํ ํ ์คํธ๋ค์ด ๊ณต์ ํ๋ ์์
- PlaywrightTestOptions: ํ ์คํธ ๋จ์ ์ค์
- PlaywrightWorkerOptions: ์์ปค ๋จ์ ์ค์
์ ๋ค ๊ฐ์ ํ์ ์ด ํฉ์ณ์ ธ ์ต์ข ์ ์ผ๋ก ํ๋์ ์ธ์ ๊ฐ์ฒด(args)๊ฐ ๋ง๋ค์ด์ง๋ค.
์ฌ๊ธฐ์ ๋ PlaywrightTestArgs์ ์ ์๋ฅผ ๋ฐ๋ผ๊ฐ ๋ณด๋ฉด:
export interface PlaywrightTestArgs {
page: Page;
context: BrowserContext;
request: APIRequestContext;
}
์ด ์ ์๋ฅผ ๋ณด๋ฉด page๊ฐ ๋จ์ํ ๊ฐ์ด ์๋๋ผ Playwright๊ฐ ๋ฏธ๋ฆฌ ์์ฑํด ์ ๋ฌํด์ฃผ๋ Page ๊ฐ์ฒด๋ผ๋ ์ ์ด ๋ช
ํํด์ง๋ค.
๊ทธ๋ฆฌ๊ณ page๋ฟ๋ง ์๋๋ผ context, request ์ญ์ ๋ชจ๋ Playwright๊ฐ ํ
์คํธ ์คํ ์ ์ ์๋์ผ๋ก ์ค๋นํด์ฃผ๋ ๊ธฐ๋ณธ fixture๋ค.
์ฆ, test()๋ ๊ฒ์ผ๋ก ๋ณด๊ธฐ์๋ ํ๋์ ์ธ์(args)๋ง ๋ฐ์ง๋ง, ์ค์ ๋ก๋ ๋ค์๊ณผ ๊ฐ์ 'ํฐ ๊ฐ์ฒด ํ๋'๋ฅผ ํ ์คํธ ํจ์๋ก ์ ๋ฌํ๋ค.
{
page: Page,
context: BrowserContext,
request: APIRequestContext,
...๊ธฐํ Playwright ๊ธฐ๋ณธ fixture๋ค,
...๋ด๊ฐ extend()๋ก ์ถ๊ฐํ custom fixture๋ค
}
์ด์ฒ๋ผ args ๋ด๋ถ์ ์๋ง์ ๊ฐ์ด ๋ค์ด ์๊ธฐ ๋๋ฌธ์, ํ ์คํธ ์ฝ๋์์๋ ํ์ํ ๊ฐ๋ง ๊ณจ๋ผ ์ฐ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ธฐ๋ณธ ํจํด์ด ๋๋ค.
๊ตฌ์กฐ ๋ถํด๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด ๋ชจ๋ fixture์ ์ ๊ทผํ ๋๋ง๋ค ๋ค์์ฒ๋ผ ์์ฑํด์ผ ํ๋ค.
test('example', async (args) => {
await args.page.goto('/');
});
์ฆ, Playwright๊ฐ ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ๊ธฐ๋ณธ ์ฌ์ฉ ๋ฐฉ์์ผ๋ก ์๋ดํ๋ ์ด์ ๋ ํ ์คํธ ํจ์์ ์ ๋ฌ๋๋ ์ธ์ ๊ฐ์ฒด๊ฐ ๋งค์ฐ ํฌ๊ณ ๋ณตํฉ์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
ํ์ํ fixture๋ง ๊ฐ๋จํ๊ฒ ๊บผ๋ด ์ธ ์ ์๋๋ก ๊ตฌ์กฐ ๋ถํด๊ฐ ์์ฐ์ค๋ฝ๊ฒ ์ต์ ์ ์ ํ์ด ๋๋ค.
๐ ๋ ๋์๊ฐ: Custom Fixture ์ฝ๋
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
// Extend basic test by providing a "todoPage" fixture.
const test = base.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
await use(todoPage);
await todoPage.removeAll();
},
});
test('should add an item', async ({ todoPage }) => {
await todoPage.addToDo('my item');
// ...
});
test('should remove an item', async ({ todoPage }) => {
await todoPage.remove('item1');
// ...
});
์ด ์ฝ๋๋ฅผ ์ฒ์ ๋ดค์ ๋, ๋ ๋ค์ ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ๋ค์ด ๋ ์ฌ๋๋ค.
- as๋ ์ ์ฐ๋ ๊ฑธ๊น?
- extend๋ ๋ฌด์จ ์ญํ ?
- <{ ... }> ๊ตฌ์กฐ๋ ๋ญ์ง?
- ({ ... }) ๊ตฌ์กฐ๋ ๋ญ์ง?
๋ค์ ํด๋น ์ง๋ฌธ๋ค์ ํ๋์ฉ ๋ฐ๋ผ๊ฐ ๋ณด๊ธฐ๋ก ํ๋ค.
๐ as: ๊ธฐ๋ณธ test ์ด๋ฆ ๋ณ๊ฒฝ
import { test as base } from '@playwright/test';
as๋ ๋จ์ํ importํ ์๋ณ์์ ์ด๋ฆ์ ๋ฐ๊พธ๋ ๋ฌธ๋ฒ์ด๋ค.
๊ธฐ๋ณธ test๋ฅผ ํ์ฅํด์ ์๋ก์ด test ๊ฐ์ฒด๋ฅผ ๋ง๋ค ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ์๋ณธ๊ณผ ๊ตฌ๋ถํ๊ธฐ ์ํด base๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฐ๊ฟ์ ๊ฐ์ ธ์ค๋ ๊ฒ์ด๋ค.
- base: Playwright๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ณธ test
- test: ๋ด๊ฐ ํ์ฅํ ์ปค์คํ test
์ด๋ ๊ฒ ์ด๋ฆ์ ๋๋์ง ์์ผ๋ฉด ํผ๋์ด ์๊ธด๋ค.
๐ Playwright์์ extend()๋ ์ด๋ค ์ญํ ์ ํ๋๊ฐ?
Playwright๋ fixture ๊ธฐ๋ฐ ํ ์คํธ ํ๋ ์์ํฌ๋ค. test()๊ฐ ๋๊ฒจ์ฃผ๋ page, context ์ญ์ ๊ธฐ๋ณธ fixture๋ค.
extend()๋ ์ฌ๊ธฐ์ ์๋ก์ด fixture๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๊ธฐ์กด fixture๋ฅผ ์ฌ์ ์ํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
const test = base.extend<{ todoPage: TodoPage }>({ ... });
์ด๋ ๊ฒ ์์ฑํ๋ฉด,
- ๊ธฐ์กด test ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ์ ์งํ๊ณ
- todoPage๋ผ๋ ์ fixture๋ฅผ ํ ์คํธ ํจ์์์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค
์ฆ, ํ ์คํธ ์คํ ํ๊ฒฝ์ ์ปค์คํฐ๋ง์ด์งํ๋ Playwright์ ์ ์ ๋ฐฉ์์ด๋ค.
๐ ์ <{ todoPage: TodoPage }>์ฒ๋ผ ์ ๋ค๋ฆญ์ ์ฐ๋๊ฐ?
์ฌ๊ธฐ์ <{ todoPage: TodoPage }>๋ TypeScript ์ ๋ค๋ฆญ์ด๋ค.
์ด ์ ๋ค๋ฆญ์ '๋ด๊ฐ ์๋ก ์ถ๊ฐํ fixture๊ฐ ์ด๋ค ํ์
์ ๊ฐ์ง๋์ง'๋ฅผ Playwright์๊ฒ ์๋ ค์ค๋ค.
- key: fixture ์ด๋ฆ (todoPage)
- value: ํด๋น fixture๊ฐ ์ด๋ค ํ์ ์ธ์ง (TodoPage ํด๋์ค)
์ด ํ์ ์ ๋ณด๋ฅผ ๋๊ฒจ๋์ด์ผ ํ ์คํธ ํจ์ ๋ด๋ถ์์ ์๋ ์์ฑ ๋ฐ ํ์ ์ถ๋ก ์ด ์ ์์ ์ผ๋ก ๋์ํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ด๊ฐ ์๋ก ์ถ๊ฐํ fixture๋ ํ ๊ฐ์ผ ์๋ ์๊ณ , ์ฌ๋ฌ ๊ฐ์ผ ์๋ ์๋ค.
Playwright๋ ์ด๋ฐ ๋ค์ํ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ์ง์ํ๊ธฐ ์ํด, ‘fixture ์ด๋ฆ: fixture ํ์ ’๋ค์ ํ๋์ ๊ฐ์ฒด ํ์ ์ผ๋ก ๋ฌถ์ด์ ์ ๋ฌํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
๋ง์ฝ fixture๊ฐ ์ฌ๋ฌ ๊ฐ๋ผ๋ฉด ์์ฐ์ค๋ฝ๊ฒ ์๋์ฒ๋ผ ํ์ฅ๋๋ค.
extend<{
todoPage: TodoPage;
userPage: UserPage;
apiClient: ApiClient;
}>
๊ฒฐ๊ตญ ์ ๋ค๋ฆญ์ {}๋ฅผ ์ฐ๋ ์ด์ ๋ ๋จ์ผ fixture๋ ์ฌ๋ฌ fixture๋ ๋์ผํ ๊ฐ์ฒด ๊ตฌ์กฐ๋ก ํ์ฅ ๊ฐ๋ฅํ๋๋ก ํ๊ธฐ ์ํด์๋ค.
๐ extend() ์์ {}๋?
extend()์ ์ ๋ฌํ๋ ๊ฐ์ฒด๋ '์ถ๊ฐํ๊ฑฐ๋ ์ฌ์ ์ํ๊ณ ์ถ์ fixture ๋ชฉ๋ก'์ ์ ์ธํ๋ ์์ญ์ด๋ค.
const test = base.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
// ...
},
});
์ฌ๊ธฐ์ ํต์ฌ์ ๋ค์ ๋ ๊ฐ์ง๋ค:
- key: fixture ์ด๋ฆ
- value: ํด๋น fixture๊ฐ ํธ์ถ๋ ๋ ์คํํ ๋ก์ง
์ค์ ๋ก๋ ์ด๋ฐ ์์๋ก ๋์ํ๋ค:
const test = base.extend<{ todoPage: TodoPage }>({
// 1) fixture ์ด๋ฆ: fixture ์์ฑ/์ด๊ธฐํ/์ ๋ฆฌ๊น์ง ๋ด๋นํ๋ ๋น๋๊ธฐ ํจ์
todoPage: async ({ page }, use) => {
// 2) Playwright๊ฐ ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ๊ธฐ๋ณธ fixture(page ๋ฑ)๋ฅผ ๋ฐ์
// ์ฐ๋ฆฌ๊ฐ ์ํ๋ fixture๋ฅผ ์์ฑํ๋ค.
const todoPage = new TodoPage(page);
// 3) ํ
์คํธ ์คํ ์ ์ ํ์ํ ์ด๊ธฐํ ์์
์ ์ํํ๋ค.
await todoPage.goto();
// 4) use()๋ฅผ ํธ์ถํด fixture๋ฅผ ํ
์คํธ์ ์ ๋ฌํ๋ค.
// → test('...', async ({ todoPage }) => { ... }) ์์ ์ ๊ทผ ๊ฐ๋ฅํด์ง
await use(todoPage);
// 5) ํ
์คํธ ์๋ฃ ํ cleanup ๋ก์ง์ ์คํํ๋ค.
await todoPage.removeAll();
},
});
'๐ Additional > ๐ Playwright' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ์ธํ๋ฐ ๊ฐ์ - Playwright ๊ธฐ์ด (๊ธฐ์ด์ ์ธ ํ์ฉ๋ฒ๊ณผ ํต์ฌ ์๋ฆฌ) (4) | 2025.06.14 |
|---|