Skip to main content

Компоненты (экспериментально)

Введение

Playwright Test теперь может тестировать ваши компоненты.

Пример

Вот как выглядит типичный тест компонента:

test('событие должно работать', async ({ mount }) => {
let clicked = false;

// Монтируем компонент. Возвращает локатор, указывающий на компонент.
const component = await mount(
<Button title="Submit" onClick={() => { clicked = true }}></Button>
);

// Как и в любом тесте Playwright, утверждаем текст локатора.
await expect(component).toContainText('Submit');

// Выполняем клик по локатору. Это вызовет событие.
await component.click();

// Утверждаем, что соответствующие события были вызваны.
expect(clicked).toBeTruthy();
});

Как начать

Добавление Playwright Test в существующий проект просто. Ниже приведены шаги для включения Playwright Test для проекта на React, Vue или Svelte.

Шаг 1: Установите Playwright Test для компонентов для вашего соответствующего фреймворка

npm init playwright@latest -- --ct

Этот шаг создаёт несколько файлов в вашем рабочем пространстве:

playwright/index.html
<html lang="en">
<body>
<div id="root"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>

Этот файл определяет html-файл, который будет использоваться для рендеринга компонентов во время тестирования. Он должен содержать элемент с id="root", именно туда монтируются компоненты. Он также должен ссылаться на скрипт под названием playwright/index.{js,ts,jsx,tsx}.

Вы можете включать таблицы стилей, применять тему и внедрять код на страницу, где монтируется компонент, используя этот скрипт. Это может быть файл .js, .ts, .jsx или .tsx.

playwright/index.ts
// Примените тему здесь, добавьте всё, что нужно вашему компоненту во время выполнения.

Шаг 2. Создайте файл теста src/App.spec.{ts,tsx}

app.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import App from './App';

test('должен работать', async ({ mount }) => {
const component = await mount(<App />);
await expect(component).toContainText('Learn React');
});

Шаг 3. Запустите тесты

Вы можете запускать тесты, используя расширение VS Code или командную строку.

npm run test-ct

Дополнительное чтение: настройка отчетности, браузеров, трассировки

Обратитесь к конфигурации Playwright для настройки вашего проекта.

Тестовые истории

Когда Playwright Test используется для тестирования веб-компонентов, тесты выполняются в Node.js, в то время как компоненты работают в реальном браузере. Это объединяет лучшее из обоих миров: компоненты работают в реальной среде браузера, выполняются реальные клики, выполняется реальная компоновка, возможна визуальная регрессия. В то же время тест может использовать все возможности Node.js, а также все функции Playwright Test. В результате те же параллельные, параметризованные тесты с той же историей постмортем-трассировки доступны во время тестирования компонентов.

Однако это вводит ряд ограничений:

  • Вы не можете передавать сложные живые объекты вашему компоненту. Можно передавать только простые объекты JavaScript и встроенные типы, такие как строки, числа, даты и т.д.
test('это будет работать', async ({ mount }) => {
const component = await mount(<ProcessViewer process={{ name: 'playwright' }}/>);
});

test('это не будет работать', async ({ mount }) => {
// `process` - это объект Node, мы не можем передать его в браузер и ожидать, что он будет работать.
const component = await mount(<ProcessViewer process={process}/>);
});
  • Вы не можете передавать данные вашему компоненту синхронно в обратном вызове:
test('это не будет работать', async ({ mount }) => {
// () => 'red' обратный вызов живет в Node. Если компонент `ColorPicker` в браузере вызовет параметрическую функцию
// `colorGetter`, он не получит результат синхронно. Он сможет получить его через await, но это не то, как
// компоненты обычно строятся.
const component = await mount(<ColorPicker colorGetter={() => 'red'}/>);
});

Обход этих и других ограничений быстрый и элегантный: для каждого случая использования тестируемого компонента создайте обертку этого компонента, специально предназначенную для тестирования. Это не только устранит ограничения, но и предложит мощные абстракции для тестирования, где вы сможете определить окружение, тему и другие аспекты рендеринга вашего компонента.

Предположим, вы хотите протестировать следующий компонент:

input-media.tsx
import React from 'react';

type InputMediaProps = {
// Media - это сложный объект браузера, который мы не можем отправить в Node во время тестирования.
onChange(media: Media): void;
};

export function InputMedia(props: InputMediaProps) {
return <></> as any;
}

Создайте файл истории для вашего компонента:

input-media.story.tsx
import React from 'react';
import InputMedia from './import-media';

type InputMediaForTestProps = {
onMediaChange(mediaName: string): void;
};

export function InputMediaForTest(props: InputMediaForTestProps) {
// Вместо отправки сложного объекта `media` в тест, отправьте имя медиа.
return <InputMedia onChange={media => props.onMediaChange(media.name)} />;
}
// Экспортируйте больше историй здесь.

Затем протестируйте компонент через тестирование истории:

input-media.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { InputMediaForTest } from './input-media.story.tsx';

test('изменяет изображение', async ({ mount }) => {
let mediaSelected: string | null = null;

const component = await mount(
<InputMediaForTest
onMediaChange={mediaName => {
mediaSelected = mediaName;
}}
/>
);
await component
.getByTestId('imageInput')
.setInputFiles('src/assets/logo.png');

await expect(component.getByAltText(/selected image/i)).toBeVisible();
await expect.poll(() => mediaSelected).toBe('logo.png');
});

В результате для каждого компонента у вас будет файл истории, который экспортирует все истории, которые фактически тестируются. Эти истории живут в браузере и "конвертируют" сложные объекты в простые объекты, которые могут быть доступны в тесте.

Под капотом

Вот как работает тестирование компонентов:

  • Как только тесты выполняются, Playwright создает список компонентов, которые нужны тестам.
  • Затем он компилирует пакет, включающий эти компоненты, и обслуживает его с помощью локального статического веб-сервера.
  • При вызове mount в тесте Playwright переходит на фасадную страницу /playwright/index.html этого пакета и сообщает ей, чтобы она отобразила компонент.
  • События маршалируются обратно в среду Node.js для проверки.

Playwright использует Vite для создания пакета компонентов и его обслуживания.

Справочник API

props

Предоставьте props компоненту при монтировании.

component.spec.tsx
import { test } from '@playwright/experimental-ct-react';

test('props', async ({ mount }) => {
const component = await mount(<Component msg="greetings" />);
});

callbacks / events

Предоставьте обратные вызовы/события компоненту при монтировании.

component.spec.tsx
import { test } from '@playwright/experimental-ct-react';

test('callback', async ({ mount }) => {
const component = await mount(<Component onClick={() => {}} />);
});

children / slots

Предоставьте children/slots компоненту при монтировании.

component.spec.tsx
import { test } from '@playwright/experimental-ct-react';

test('children', async ({ mount }) => {
const component = await mount(<Component>Child</Component>);
});

hooks

Вы можете использовать хуки beforeMount и afterMount для настройки вашего приложения. Это позволяет вам настраивать такие вещи, как маршрутизатор вашего приложения, фейковый сервер и т.д., предоставляя вам необходимую гибкость. Вы также можете передавать пользовательскую конфигурацию из вызова mount из теста, которая доступна из фикстуры hooksConfig. Это включает любую конфигурацию, которую нужно выполнить до или после монтирования компонента. Пример настройки маршрутизатора приведен ниже:

playwright/index.tsx
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';
import { BrowserRouter } from 'react-router-dom';

export type HooksConfig = {
enableRouting?: boolean;
}

beforeMount<HooksConfig>(async ({ App, hooksConfig }) => {
if (hooksConfig?.enableRouting)
return <BrowserRouter><App /></BrowserRouter>;
});
src/pages/ProductsPage.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import type { HooksConfig } from '../playwright';
import { ProductsPage } from './pages/ProductsPage';

test('настройка маршрутизации через конфигурацию хуков', async ({ page, mount }) => {
const component = await mount<HooksConfig>(<ProductsPage />, {
hooksConfig: { enableRouting: true },
});
await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42');
});

unmount

Демонтируйте смонтированный компонент из DOM. Это полезно для тестирования поведения компонента при демонтаже. Примеры использования включают тестирование модального окна "Вы уверены, что хотите покинуть?" или обеспечение правильной очистки обработчиков событий для предотвращения утечек памяти.

component.spec.tsx
import { test } from '@playwright/experimental-ct-react';

test('unmount', async ({ mount }) => {
const component = await mount(<Component/>);
await component.unmount();
});

update

Обновите props, slots/children и/или события/обратные вызовы смонтированного компонента. Эти входные данные компонента могут изменяться в любое время и обычно предоставляются родительским компонентом, но иногда необходимо убедиться, что ваши компоненты ведут себя правильно при новых входных данных.

component.spec.tsx
import { test } from '@playwright/experimental-ct-react';

test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update(
<Component msg="greetings" onClick={() => {}}>Child</Component>
);
});

Обработка сетевых запросов

Playwright предоставляет экспериментальную фикстуру router для перехвата и обработки сетевых запросов. Есть два способа использования фикстуры router:

  • Вызовите router.route(url, handler), который ведет себя аналогично page.route(). См. руководство по мокированию сети для получения более подробной информации.
  • Вызовите router.use(handlers) и передайте обработчики запросов библиотеки MSW в него.

Вот пример повторного использования ваших существующих обработчиков MSW в тесте.

import { handlers } from '@src/mocks/handlers';

test.beforeEach(async ({ router }) => {
// установите общие обработчики перед каждым тестом
await router.use(...handlers);
});

test('пример теста', async ({ mount }) => {
// тестируйте как обычно, ваши обработчики активны
// ...
});

Вы также можете ввести одноразовый обработчик для конкретного теста.

import { http, HttpResponse } from 'msw';

test('пример теста', async ({ mount, router }) => {
await router.use(http.get('/data', async ({ request }) => {
return HttpResponse.json({ value: 'mocked' });
}));

// тестируйте как обычно, ваш обработчик активен
// ...
});

Часто задаваемые вопросы

В чем разница между @playwright/test и @playwright/experimental-ct-{react,svelte,vue}?

test('…', async ({ mount, page, context }) => {
// …
});

@playwright/experimental-ct-{react,svelte,vue} оборачивает @playwright/test, чтобы предоставить дополнительную встроенную фикстуру, специфичную для тестирования компонентов, под названием mount:

import { test, expect } from '@playwright/experimental-ct-react';
import HelloWorld from './HelloWorld';

test.use({ viewport: { width: 500, height: 500 } });

test('должен работать', async ({ mount }) => {
const component = await mount(<HelloWorld msg="greetings" />);
await expect(component).toContainText('Greetings');
});

Кроме того, он добавляет некоторые параметры конфигурации, которые вы можете использовать в вашем playwright-ct.config.{ts,js}.

Наконец, под капотом, каждый тест повторно использует фикстуры context и page в качестве оптимизации скорости для тестирования компонентов. Он сбрасывает их между каждым тестом, так что это должно быть функционально эквивалентно гарантии @playwright/test, что вы получаете новую, изолированную фикстуру context и page для каждого теста.

У меня есть проект, который уже использует Vite. Могу ли я повторно использовать конфигурацию?

На данный момент Playwright не зависит от сборщика, поэтому он не использует вашу существующую конфигурацию Vite. Ваша конфигурация может содержать много вещей, которые мы не сможем использовать повторно. Поэтому на данный момент вы можете скопировать ваши сопоставления путей и другие настройки высокого уровня в свойство ctViteConfig конфигурации Playwright.

import { defineConfig } from '@playwright/experimental-ct-react';

export default defineConfig({
use: {
ctViteConfig: {
// ...
},
},
});

Вы можете указать плагины через конфигурацию Vite для тестирования настроек. Обратите внимание, что как только вы начнете указывать плагины, вы несете ответственность за указание плагина фреймворка, например, vue() в этом случае:

import { defineConfig, devices } from '@playwright/experimental-ct-vue';

import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';

export default defineConfig({
testDir: './tests/component',
use: {
trace: 'on-first-retry',
ctViteConfig: {
plugins: [
vue(),
AutoImport({
imports: [
'vue',
'vue-router',
'@vueuse/head',
'pinia',
{
'@/store': ['useStore'],
},
],
dts: 'src/auto-imports.d.ts',
eslintrc: {
enabled: true,
},
}),
Components({
dirs: ['src/components'],
extensions: ['vue'],
}),
],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
},
},
});

Как использовать импорты CSS?

Если у вас есть компонент, который импортирует CSS, Vite обработает это автоматически. Вы также можете использовать препроцессоры CSS, такие как Sass, Less или Stylus, и Vite обработает их также без дополнительной конфигурации. Однако соответствующий препроцессор CSS должен быть установлен.

Vite имеет жесткое требование, чтобы все модули CSS назывались *.module.[css extension]. Если у вас есть пользовательская конфигурация сборки для вашего проекта и есть импорты в виде import styles from 'styles.css', вы должны переименовать ваши файлы, чтобы правильно указать, что они должны обрабатываться как модули. Вы также можете написать плагин Vite, чтобы обработать это за вас.

Проверьте документацию Vite для получения более подробной информации.

Как я могу тестировать компоненты, использующие Pinia?

Pinia должна быть инициализирована в playwright/index.{js,ts,jsx,tsx}. Если вы сделаете это внутри хука beforeMount, initialState может быть перезаписано на основе каждого теста:

playwright/index.ts
import { beforeMount, afterMount } from '@playwright/experimental-ct-vue/hooks';
import { createTestingPinia } from '@pinia/testing';
import type { StoreState } from 'pinia';
import type { useStore } from '../src/store';

export type HooksConfig = {
store?: StoreState<ReturnType<typeof useStore>>;
}

beforeMount<HooksConfig>(async ({ hooksConfig }) => {
createTestingPinia({
initialState: hooksConfig?.store,
/**
* Используйте перехват http для мокирования вызовов API вместо этого:
* https://playwright.dev/docs/mock#mock-api-requests
*/
stubActions: false,
createSpy(args) {
console.log('spy', args)
return () => console.log('spy-returns')
},
});
});
src/pinia.spec.ts
import { test, expect } from '@playwright/experimental-ct-vue';
import type { HooksConfig } from '../playwright';
import Store from './Store.vue';

test('переопределение initialState', async ({ mount }) => {
const component = await mount<HooksConfig>(Store, {
hooksConfig: {
store: { name: 'override initialState' }
}
});
await expect(component).toContainText('override initialState');
});

Как получить доступ к методам компонента или его экземпляру?

Доступ к внутренним методам компонента или его экземпляру в тестовом коде не рекомендуется и не поддерживается. Вместо этого сосредоточьтесь на наблюдении и взаимодействии с компонентом с точки зрения пользователя, обычно путем клика или проверки видимости чего-либо на странице. Тесты становятся менее хрупкими и более ценными, когда они избегают взаимодействия с внутренними деталями реализации, такими как экземпляр компонента или его методы. Имейте в виду, что если тест не проходит при запуске с точки зрения пользователя, это, вероятно, означает, что автоматизированный тест обнаружил реальную ошибку в вашем коде.