Забытый await перед Playwright-ассерцией даёт ошибку «Argument of type 'Locator' is not assignable to parameter of type 'string'» в рантайме. TypeScript показывает ту же ошибку прямо в редакторе, до запуска теста. Playwright компилирует TypeScript внутри себя без отдельного шага сборки, а автодополнение которое он открывает избавляет от необходимости помнить весь API наизусть. Статья разбирает TypeScript-дополнения которые реально нужны в тестовом коде: аннотации типов, интерфейсы для тестовых данных, типизированные Page Object-ы и четыре ошибки с которыми сталкиваются чаще всего.

Что TypeScript добавляет к JavaScript

TypeScript: JavaScript с типами. Всё что работает в JavaScript, работает в TypeScript. Добавляется только слой аннотаций поверх.

// JavaScript
function getUser(id) {
  return fetchUser(id);
}

// TypeScript — та же функция с типами
function getUser(id: number): Promise<User> {
  return fetchUser(id);
}

Аннотация : number говорит TypeScript что id должен быть числом. : Promise описывает что возвращает функция. Если случайно передать строку вместо числа, TypeScript сообщит об этом сразу, до запуска кода.

Почему TypeScript важен именно для Playwright

Автодополнение. Когда пишешь page. в VS Code, TypeScript знает все методы которые существуют на объекте page и показывает их. Не нужно помнить API. Поимка пропущенного await. Забытый await перед ассерцией? TypeScript сразу укажет:

Argument of type 'Locator' is not assignable to parameter of type 'string'

Безопасность Page Object. Когда вызываешь методы класса Page Object, TypeScript проверяет что метод существует и типы аргументов правильные. Опечатка в имени метода ловится мгновенно. Типы Playwright первоклассные. Команда Playwright поддерживает TypeScript-типы как основную часть проекта. Автодополнение и проверка типов работают без дополнительной настройки.

Настройка TypeScript для Playwright

При инициализации проекта Playwright TypeScript доступен как опция:

npm init playwright@latest

Выбираешь TypeScript в диалоге. Создаются playwright.config.ts (конфиг на TypeScript), tests/example.spec.ts (пример теста на TypeScript) и tsconfig.json (конфигурация TypeScript).

Если уже есть JavaScript-проект: переименовываешь .js-файлы в .ts и добавляешь tsconfig.json.

Базовый синтаксис TypeScript для тестового кода

Аннотации типов

// Примитивные типы
const email: string = 'admin@becomeqa.com';
const timeout: number = 30000;
const isLoggedIn: boolean = false;

// На практике TypeScript выводит типы сам — аннотации нужны редко
const email = 'admin@becomeqa.com'; // TypeScript знает что это строка

TypeScript выводит типы из значений. Аннотации нужно писать явно только когда TypeScript не может определить тип самостоятельно.

Интерфейсы: описание формы объектов

// Описываем как должен выглядеть объект пользователя
interface User {
  email: string;
  password: string;
  role: 'admin' | 'viewer';
}

// TypeScript теперь знает что ожидать
const testUser: User = {
  email: 'admin@becomeqa.com',
  password: 'testpass123',
  role: 'admin',
};

// TypeScript выдаст ошибку если добавить несуществующее поле
const badUser: User = {
  email: 'admin@becomeqa.com',
  password: 'testpass123',
  role: 'admin',
  name: 'Alice', // Error: 'name' does not exist in type 'User'
};

В тестах интерфейсы полезнее всего для тестовых данных и параметров конструкторов Page Object.

Union-типы

// 'admin' | 'viewer' означает что значение может быть только одним из двух
type UserRole = 'admin' | 'viewer';

// Удобно для статусов и полей с фиксированным набором значений
type ItemStatus = 'Planned' | 'In Progress' | 'Completed';

function selectStatus(status: ItemStatus) {
  // TypeScript знает что status — одно из трёх конкретных значений
}

selectStatus('Planned');    // корректно
selectStatus('Cancelled'); // Error: not assignable to type 'ItemStatus'

Union-типы не дают невалидным значениям попасть в тестовые данные.

Опциональные свойства

interface TestItem {
  destination: string;
  status: ItemStatus;
  notes?: string; // ? означает что свойство необязательное
}

// Оба варианта корректны
const item1: TestItem = { destination: 'Tokyo', status: 'Planned' };
const item2: TestItem = { destination: 'Paris', status: 'Completed', notes: 'Visited' };

TypeScript в Page Object Model

Настоящая польза TypeScript для QA-инженеров раскрывается в Page Object-ах:

import { Page } from '@playwright/test';

export class LoginPage {
  // TypeScript знает что page — это Playwright-объект Page
  constructor(private page: Page) {}

  async login(email: string, password: string): Promise<void> {
    await this.page.getByRole('button', { name: 'Login' }).click();
    await this.page.getByLabel('Username').fill(email);
    await this.page.getByLabel('Password').fill(password);
    await this.page.getByRole('button', { name: 'Submit' }).click();
  }

  async getErrorMessage(): Promise<string | null> {
    const error = this.page.getByRole('alert');
    if (await error.isVisible()) {
      return error.textContent();
    }
    return null;
  }
}

Использование в тесте:

test('login with invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  
  // TypeScript знает что login() принимает две строки
  await loginPage.login('wrong@example.com', 'wrongpass');
  
  // TypeScript знает что getErrorMessage() возвращает Promise<string | null>
  const error = await loginPage.getErrorMessage();
  expect(error).toBeTruthy();
});

Если написать loginPage.loginn() (опечатка), TypeScript поймает сразу. Если написать loginPage.login(123, 'password') (неверный тип), TypeScript выдаст ошибку.

Встроенные типы Playwright которые понадобятся

import { Page, BrowserContext, Locator, APIRequestContext } from '@playwright/test';

// Page — основной объект страницы браузера
// BrowserContext — изолированная браузерная сессия
// Locator — ссылка на элементы страницы
// APIRequestContext — для API-тестирования

Импортировать нужно когда пишешь типизированные Page Object-ы или файлы с фикстурами.

Типизированные тестовые фикстуры

Когда расширяешь стандартные фикстуры Playwright для переиспользования Page Object-ов, TypeScript делает это надёжно:

import { test as base, Page } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

// Описываем тип кастомных фикстур
type Fixtures = {
  loginPage: LoginPage;
};

// Расширяем базовый test типизированными фикстурами
export const test = base.extend<Fixtures>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },
});

// В тестах TypeScript знает что loginPage — экземпляр LoginPage
test('test with fixture', async ({ loginPage }) => {
  await loginPage.login('admin@becomeqa.com', 'testpass123');
});

tsconfig.json: что важно

tsconfig.json который создаётся при npm init playwright@latest работает сразу. Настройки которые стоит знать:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist"
  }
}

"strict": true включает все строгие проверки TypeScript: запрет неявного типа any, проверки на null (TypeScript предупреждает если вызываешь метод на null) и проверка не-await-нутых Promise.

Оставляй strict: true. Предупреждения которые он генерирует почти всегда указывают на реальные проблемы.

Что не нужно учить прямо сейчас

В TypeScript есть возможности для разработки крупных приложений, которые в тестовом коде встречаются редко:

  • Generics: нужны только если строишь переиспользуемые утилиты для фикстур
  • Декораторы: использовались в старых фреймворках, в современном Playwright не нужны
  • Utility-типы (Partial, Readonly): иногда полезны, но не обязательны
  • Declaration-файлы (.d.ts): для авторов библиотек, не для тест-разработчиков

Изучай их когда столкнёшься с конкретной проблемой которую они решают.

Частые TypeScript-ошибки в Playwright-тестах

Property 'X' does not exist on type 'Y'

Вызывается метод или свойство которого не существует. Либо опечатка, либо нужно импортировать правильный тип.

Object is possibly 'null'

TypeScript знает что метод может вернуть null. Нужно проверить возвращаемое значение:

const text = await page.getByRole('heading').textContent();
// TypeScript: text может быть null

if (text !== null) {
  expect(text).toBe('My Travel Items');
}
// Или non-null assertion если уверен в результате:
expect(text!).toBe('My Travel Items');

Argument of type 'string' is not assignable to parameter of type 'number'

В функцию передан неверный тип. Смотри сигнатуру функции и исправляй аргумент.

'await' has no effect on the type of this expression

await используется перед чем-то что не является Promise. Убираешь await или проверяешь почему метод не возвращает Promise.

JavaScript или TypeScript: практический выбор

Начинаешь с нуля: бери TypeScript. Порог вхождения по сравнению с чистым JavaScript минимальный, а польза (автодополнение, поимка ошибок) окупается сразу.

Есть существующий JavaScript-проект Playwright: рассмотри миграцию. Переименовываешь .js в .ts, добавляешь tsconfig.json, исправляешь ошибки типов. Для типичного проекта это несколько часов.

Команда использует JavaScript: TypeScript-файлы компилируются в JavaScript и спокойно сосуществуют. Мигрировать можно файл за файлом.

Частые вопросы

Нужно ли компилировать TypeScript перед запуском тестов?

Нет. Playwright обрабатывает компиляцию внутри. Запускаешь npx playwright test как обычно. Playwright компилирует TypeScript на лету встроенным транспайлером.

TypeScript показывает ошибки, но тесты всё равно запускаются. Почему?

Ошибки TypeScript: предупреждения от тайп-чекера, не рантаймовые ошибки. Компилятор транспилирует код независимо от ошибок типов. Смысл их исправлять: поймать баги до того как они превратятся в рантаймовые падения, а не чтобы заставить тесты запуститься.

Нужно ли понимать все TypeScript-типы которые использует Playwright?

Нет. Для написания типизированных Page Object-ов нужно уметь импортировать Page, Locator и APIRequestContext. Остальное узнаёшь по мере необходимости.

TypeScript сложнее JavaScript для написания тестов?

Значимые дополнения для тестового кода: аннотации типов на параметрах функций, интерфейсы для тестовых данных, импорт Playwright-типов. Если уже знаешь JavaScript, освоить это займёт несколько дней, не недель.

→ See also: Page Object Model в Playwright: от хаоса к поддерживаемым тестам | TypeScript интерфейсы и типы для Page Object Model