Типизация в Playwright
Урок: Типизация в Playwright (page, locator, test)
Введение
К этому моменту у тебя уже есть вся база:
- ты знаешь, что такое TypeScript;
- умеешь работать с базовыми типами;
- типизируешь переменные и функции;
- используешь интерфейсы;
- понимаешь, как задавать типы входных и выходных данных.
Теперь самое важное — применить всё это в реальном инструменте. И здесь появляется Playwright.
Когда ты пишешь тест:
- ты работаешь со страницей (
page); - находишь элементы (
locator); - пишешь сами тесты (
test).
Если не использовать типы:
- легко ошибиться в методах;
- не очевидно, что возвращает функция;
- сложно читать код.
TypeScript решает это — он делает работу с Playwright предсказуемой.
Главная идея урока: мы учимся правильно использовать встроенные типы Playwright, чтобы писать надёжные и понятные тесты.
Что даёт TypeScript в Playwright
Когда ты используешь Playwright с TypeScript, ты получаешь:
- автоподсказки методов (
page.click,locator.fillи т.д.); - проверку параметров;
- понимание, что возвращает каждая операция;
- защиту от ошибок ещё до запуска теста.
Это особенно важно, потому что тесты:
- часто длинные;
- работают с асинхронным кодом;
- взаимодействуют с UI.
Тип page
page — это главный объект, через который ты управляешь браузером.
В реальном проекте у него тип Page из @playwright/test. Ниже — упрощённый мок с теми же сигнатурами методов:
type MockLocator = {
click: () => Promise<void>;
fill: (text: string) => Promise<void>;
};
type MockPage = {
goto: (url: string) => Promise<void>;
locator: (selector: string) => MockLocator;
fill: (selector: string, text: string) => Promise<void>;
click: (selector: string) => Promise<void>;
};
const test = (
name: string,
fn: (ctx: { page: MockPage }) => Promise<void>,
): Promise<void> => {
console.log('→ test:', name);
const page: MockPage = {
goto: async (url) => console.log('page.goto', url),
locator: (sel) => ({
click: async () => console.log('locator.click', sel),
fill: async (text) => console.log('locator.fill', sel, text),
}),
fill: async (sel, text) => console.log('page.fill', sel, text),
click: async (sel) => console.log('page.click', sel),
};
return fn({ page });
};
void (async () => {
await test('open page', async ({ page }) => {
await page.goto('https://example.com');
});
})();
Что здесь происходит:
- в Playwright тип называется
Page; - фикстура теста передаёт
{ page: Page }; - TypeScript знает список методов у
page.
Благодаря этому:
- появляются подсказки методов;
- нельзя вызвать несуществующий метод (на этапе проверки типов).
Упрощённый вариант (часто используемый)
Обычно Playwright уже типизирует page автоматически — тип выводится из сигнатуры test:
type MockLocator = {
click: () => Promise<void>;
fill: (text: string) => Promise<void>;
};
type MockPage = {
goto: (url: string) => Promise<void>;
locator: (selector: string) => MockLocator;
fill: (selector: string, text: string) => Promise<void>;
click: (selector: string) => Promise<void>;
};
const test = (
name: string,
fn: (ctx: { page: MockPage }) => Promise<void>,
): Promise<void> => {
console.log('→ test:', name);
const page: MockPage = {
goto: async (url) => console.log('page.goto', url),
locator: (sel) => ({
click: async () => console.log('locator.click', sel),
fill: async (text) => console.log('locator.fill', sel, text),
}),
fill: async (sel, text) => console.log('page.fill', sel, text),
click: async (sel) => console.log('page.click', sel),
};
return fn({ page });
};
void (async () => {
await test('open page', async ({ page }) => {
await page.goto('https://example.com');
});
})();
TypeScript сам понимает, что page соответствует объявленному типу.
Но важно знать, что в Playwright этот тип называется Page и что он означает.
Тип Locator
locator — это объект, который представляет элемент на странице.
В Playwright у него тип Locator. Пример с моком:
type MockLocator = {
click: () => Promise<void>;
fill: (text: string) => Promise<void>;
};
type MockPage = {
goto: (url: string) => Promise<void>;
locator: (selector: string) => MockLocator;
fill: (selector: string, text: string) => Promise<void>;
click: (selector: string) => Promise<void>;
};
const test = (
name: string,
fn: (ctx: { page: MockPage }) => Promise<void>,
): Promise<void> => {
console.log('→ test:', name);
const page: MockPage = {
goto: async (url) => console.log('page.goto', url),
locator: (sel) => ({
click: async () => console.log('locator.click', sel),
fill: async (text) => console.log('locator.fill', sel, text),
}),
fill: async (sel, text) => console.log('page.fill', sel, text),
click: async (sel) => console.log('page.click', sel),
};
return fn({ page });
};
void (async () => {
await test('click button', async ({ page }) => {
const button: MockLocator = page.locator('#submit');
await button.click();
});
})();
Что здесь важно:
Locator— это не просто элемент;- это «умный» объект, который умеет:
- ждать появления элемента;
- выполнять действия;
- работать стабильно.
TypeScript знает, какие методы доступны у Locator:
click()fill()textContent()
Если написать вызов несуществующего метода, проверка типов это отловит. В чистом JavaScript ошибка всплывёт только в рантайме:
const button: { click: () => Promise<void> } = {
click: async () => console.log('click'),
};
try {
(button as any).notExistingMethod();
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
console.log('в TS такого метода нет; в JS:', msg);
}
Тип test
test — это функция, которая создаёт тест.
Когда ты пишешь:
type MockLocator = {
click: () => Promise<void>;
fill: (text: string) => Promise<void>;
};
type MockPage = {
goto: (url: string) => Promise<void>;
locator: (selector: string) => MockLocator;
fill: (selector: string, text: string) => Promise<void>;
click: (selector: string) => Promise<void>;
};
const test = (
name: string,
fn: (ctx: { page: MockPage }) => Promise<void>,
): Promise<void> => {
console.log('→ test:', name);
const page: MockPage = {
goto: async (url) => console.log('page.goto', url),
locator: (sel) => ({
click: async () => console.log('locator.click', sel),
fill: async (text) => console.log('locator.fill', sel, text),
}),
fill: async (sel, text) => console.log('page.fill', sel, text),
click: async (sel) => console.log('page.click', sel),
};
return fn({ page });
};
void (async () => {
await test('my test', async ({ page }) => {
await page.goto('https://example.com');
});
})();
TypeScript:
- знает структуру аргументов колбэка;
- понимает, какие объекты доступны (
page,browser, и т.д. в реальном API); - помогает не ошибиться в использовании.
Асинхронность и типы
Playwright активно использует async/await.
Пример:
void (async () => {
const page: { title: () => Promise<string> } = {
title: async () => 'Example Domain',
};
const title = await page.title();
console.log('title:', title);
})();
TypeScript знает, что:
page.title()возвращаетPromise<string>;- после
awaitрезультат — этоstring.
Поэтому после await ты работаешь уже со строкой:
void (async () => {
const page: { title: () => Promise<string> } = {
title: async () => 'Example Domain',
};
const title: string = await page.title();
console.log('длина заголовка:', title.length);
})();
Если написать (в TypeScript) что-то вроде «сложить строку с числом» для переменной, которую ты ожидаешь как string, компилятор покажет несоответствие. В чистом JS это просто выполнится:
void (async () => {
const page: { title: () => Promise<string> } = {
title: async () => 'Example Domain',
};
const title = await page.title();
console.log('конкатенация (JS допускает):', title + String(123));
})();
TypeScript сразу покажет несоответствие, если тип результата не совпадает с ожидаемым (например, при строгих проверках или неверной аннотации).
Пример полного теста с типами
type MockLocator = {
click: () => Promise<void>;
fill: (text: string) => Promise<void>;
};
type MockPage = {
goto: (url: string) => Promise<void>;
locator: (selector: string) => MockLocator;
fill: (selector: string, text: string) => Promise<void>;
click: (selector: string) => Promise<void>;
url: () => string;
};
type MockExpect = {
toBeVisible: () => Promise<void>;
toHaveURL: (u: string) => Promise<void>;
toContain: (sub: string) => Promise<void>;
};
const test = (
name: string,
fn: (ctx: { page: MockPage }) => Promise<void>,
): Promise<void> => {
console.log('→ test:', name);
const page: MockPage = {
goto: async (url) => console.log('page.goto', url),
locator: (sel) => ({
click: async () => console.log('locator.click', sel),
fill: async (text) => console.log('locator.fill', sel, text),
}),
fill: async (sel, text) => console.log('page.fill', sel, text),
click: async (sel) => console.log('page.click', sel),
url: () => 'https://app.example/dashboard',
};
return fn({ page });
};
const expect = (value: string): MockExpect => ({
toBeVisible: async () => console.log('expect.toBeVisible'),
toHaveURL: async (u) => console.log('expect.toHaveURL', u),
toContain: async (sub) => console.log('expect.toContain', value, sub),
});
void (async () => {
await test('login test', async ({ page }) => {
const usernameInput: MockLocator = page.locator('#username');
const passwordInput: MockLocator = page.locator('#password');
const loginButton: MockLocator = page.locator('#login');
await usernameInput.fill('admin');
await passwordInput.fill('1234');
await loginButton.click();
const url: string = page.url();
await expect(url).toContain('dashboard');
});
})();
Разбор:
Locatorиспользуется для элементов;string— для результатаpage.url();- все типы понятны и проверяются.
Зачем это нужно в реальной работе
Типизация в Playwright даёт:
1. Меньше ошибок
Ты не можешь:
- вызвать несуществующий метод;
- передать неправильный тип;
- забыть про асинхронность.
2. Быструю разработку
Редактор подсказывает:
- какие методы есть у
page; - какие параметры нужны;
- что возвращает функция.
3. Понятный код
Когда ты видишь:
const button: { click: () => Promise<void> } = {
click: async () => console.log('locator.click', 'button'),
};
console.log('переменная — как Locator: известен набор методов');
ты сразу понимаешь:
- это элемент страницы;
- с ним можно делать действия.
В Playwright вместо ручного объекта будет const button: Locator = page.locator('button');.
console.log(
'Подсказка: типы Page и Locator приходят из @playwright/test; через import type редактор знает сигнатуры методов.',
);
Связь со следующим уроком
Сейчас ты работаешь напрямую с:
page;locator.
Но в реальных проектах так не делают.
Вместо этого используют Page Object:
- создают классы;
- выносят логику работы со страницей;
- добавляют типизацию.
И все типы (Page, Locator, функции) станут частью этих классов.
Итоговое понимание
TypeScript в Playwright — это не просто «дополнение», а основа удобной работы.
Он позволяет:
- точно понимать, с чем ты работаешь (
Page,Locator); - контролировать данные;
- избегать ошибок ещё до запуска теста.
Главная мысль урока:
Типы в Playwright превращают тесты из «набора действий» в структурированный и предсказуемый код, с которым легко работать и который сложно сломать.
И следующий шаг — перенести всё это в Page Object, где типизация станет ещё более важной.