Spec-Zone.ru › Playwright

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

Введение​

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

Пример​

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

test('event should work', async ({ mount }) => {
  let clicked = false;

  // Mount a component. Returns locator pointing to the component.
  const component = await mount(
    <Button title="Submit" onClick={() => { clicked = true }}></Button>
  );

  // As with any Playwright test, assert locator text.
  await expect(component).toContainText('Submit');

  // Perform locator click. This will trigger the event.
  await component.click();

  // Assert that respective events have been fired.
  expect(clicked).toBeTruthy();
});

Начало работы​

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

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

  • npm
  • yarn
  • pnpm
npm init playwright@latest -- --ct
yarn create playwright --ct
pnpm create playwright --ct

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

<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.

// Apply theme here, add anything your component needs at runtime here.

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

  • React
  • Svelte
  • Vue
import { test, expect } from '@playwright/experimental-ct-react';
import App from './App';

test('should work', async ({ mount }) => {
  const component = await mount(<App />);
  await expect(component).toContainText('Learn React');
});
import { test, expect } from '@playwright/experimental-ct-vue';
import App from './App.vue';

test('should work', async ({ mount }) => {
  const component = await mount(App);
  await expect(component).toContainText('Learn Vue');
});
import { test, expect } from '@playwright/experimental-ct-vue';
import App from './App.vue';

test('should work', async ({ mount }) => {
  const component = await mount(<App />);
  await expect(component).toContainText('Learn Vue');
});

Если вы используете TypeScript и Vue, убедитесь, что добавили файл vue.d.ts в свой проект:

declare module '*.vue';
import { test, expect } from '@playwright/experimental-ct-svelte';
import App from './App.svelte';

test('should work', async ({ mount }) => {
  const component = await mount(App);
  await expect(component).toContainText('Learn Svelte');
});

Шаг 3. Запуск тестов​

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

npm run test-ct

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

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

Истории тестов​

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

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

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

test('this will not work', async ({ mount }) => {
  // `process` is a Node object, we can't pass it to the browser and expect it to work.
  const component = await mount(<ProcessViewer process={process}/>);
});
  • Вы не можете передавать данные своему компоненту синхронно в обратном вызове:
test('this will not work', async ({ mount }) => {
  // () => 'red' callback lives in Node. If `ColorPicker` component in the browser calls the parameter function
  // `colorGetter` it won't get result synchronously. It'll be able to get it via await, but that is not how
  // components are typically built.
  const component = await mount(<ColorPicker colorGetter={() => 'red'}/>);
});

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

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

import React from 'react';

type InputMediaProps = {
  // Media is a complex browser object we can't send to Node while testing.
  onChange(media: Media): void;
};

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

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

import React from 'react';
import InputMedia from './import-media';

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

export function InputMediaForTest(props: InputMediaForTestProps) {
  // Instead of sending a complex `media` object to the test, send the media name.
  return <InputMedia onChange={media => props.onMediaChange(media.name)} />;
}
// Export more stories here.

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

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

test('changes the image', 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​

Свойства​

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

  • React
  • Svelte
  • Vue
import { test } from '@playwright/experimental-ct-react';

test('props', async ({ mount }) => {
  const component = await mount(<Component msg="greetings" />);
});
import { test } from '@playwright/experimental-ct-svelte';

test('props', async ({ mount }) => {
  const component = await mount(Component, { props: { msg: 'greetings' } });
});
import { test } from '@playwright/experimental-ct-vue';

test('props', async ({ mount }) => {
  const component = await mount(Component, { props: { msg: 'greetings' } });
});
// Or alternatively, using the `jsx` style
import { test } from '@playwright/experimental-ct-vue';

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

Обратные вызовы / события​

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

  • React
  • Svelte
  • Vue
import { test } from '@playwright/experimental-ct-react';

test('callback', async ({ mount }) => {
  const component = await mount(<Component onClick={() => {}} />);
});
import { test } from '@playwright/experimental-ct-svelte';

test('event', async ({ mount }) => {
  const component = await mount(Component, { on: { click() {} } });
});
import { test } from '@playwright/experimental-ct-vue';

test('event', async ({ mount }) => {
  const component = await mount(Component, { on: { click() {} } });
});
// Or alternatively, using the `jsx` style
import { test } from '@playwright/experimental-ct-vue';

test('event', async ({ mount }) => {
  const component = await mount(<Component v-on:click={() => {}} />);
});

Дочерние элементы / слоты​

Предоставьте дочерние элементы/слоты компоненту при его монтировании.

  • React
  • Svelte
  • Vue
import { test } from '@playwright/experimental-ct-react';

test('children', async ({ mount }) => {
  const component = await mount(<Component>Child</Component>);
});
import { test } from '@playwright/experimental-ct-svelte';

test('slot', async ({ mount }) => {
  const component = await mount(Component, { slots: { default: 'Slot' } });
});
import { test } from '@playwright/experimental-ct-vue';

test('slot', async ({ mount }) => {
  const component = await mount(Component, { slots: { default: 'Slot' } });
});
// Or alternatively, using the `jsx` style
import { test } from '@playwright/experimental-ct-vue';

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

Хуксы​

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

  • React
  • Vue3
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>;
});
import { test, expect } from '@playwright/experimental-ct-react';
import type { HooksConfig } from '../playwright';
import { ProductsPage } from './pages/ProductsPage';

test('configure routing through hooks config', async ({ page, mount }) => {
  const component = await mount<HooksConfig>(<ProductsPage />, {
    hooksConfig: { enableRouting: true },
  });
  await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42');
});
import { beforeMount, afterMount } from '@playwright/experimental-ct-vue/hooks';
import { router } from '../src/router';

export type HooksConfig = {
  enableRouting?: boolean;
}

beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
  if (hooksConfig?.enableRouting)
    app.use(router);
});
import { test, expect } from '@playwright/experimental-ct-vue';
import type { HooksConfig } from '../playwright';
import ProductsPage from './pages/ProductsPage.vue';

test('configure routing through hooks config', async ({ page, mount }) => {
  const component = await mount<HooksConfig>(ProductsPage, {
    hooksConfig: { enableRouting: true },
  });
  await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42');
});

Размонтирование​

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

  • React
  • Svelte
  • Vue
import { test } from '@playwright/experimental-ct-react';

test('unmount', async ({ mount }) => {
  const component = await mount(<Component/>);
  await component.unmount();
});
import { test } from '@playwright/experimental-ct-svelte';

test('unmount', async ({ mount }) => {
  const component = await mount(Component);
  await component.unmount();
});
import { test } from '@playwright/experimental-ct-vue';

test('unmount', async ({ mount }) => {
  const component = await mount(Component);
  await component.unmount();
});
// Or alternatively, using the `jsx` style
import { test } from '@playwright/experimental-ct-vue';

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

Обновление​

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

  • React
  • Svelte
  • Vue
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>
  );
});
import { test } from '@playwright/experimental-ct-svelte';

test('update', async ({ mount }) => {
  const component = await mount(Component);
  await component.update({
    props: { msg: 'greetings' },
    on: { click() {} },
    slots: { default: 'Child' }
  });
});
import { test } from '@playwright/experimental-ct-vue';

test('update', async ({ mount }) => {
  const component = await mount(Component);
  await component.update({
    props: { msg: 'greetings' },
    on: { click() {} },
    slots: { default: 'Child' }
  });
});
// Or alternatively, using the `jsx` style
import { test } from '@playwright/experimental-ct-vue';

test('update', async ({ mount }) => {
  const component = await mount(<Component/>);
  await component.update(
      <Component msg="greetings" v-on:click={() => {}}>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 }) => {
  // install common handlers before each test
  await router.use(...handlers);
});

test('example test', async ({ mount }) => {
  // test as usual, your handlers are active
  // ...
});

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

import { http, HttpResponse } from 'msw';

test('example test', async ({ mount, router }) => {
  await router.use(http.get('/data', async ({ request }) => {
    return HttpResponse.json({ value: 'mocked' });
  }));

  // test as usual, your handler is active
  // ...
});

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

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

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

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

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

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

test('should work', async ({ mount }) => {
  const component = await mount(<HelloWorld msg="greetings" />);
  await expect(component).toContainText('Greetings');
});
import { test, expect } from '@playwright/experimental-ct-vue';
import HelloWorld from './HelloWorld.vue';

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

test('should work', async ({ mount }) => {
  const component = await mount(HelloWorld, {
    props: {
      msg: 'Greetings',
    },
  });
  await expect(component).toContainText('Greetings');
});
import { test, expect } from '@playwright/experimental-ct-svelte';
import HelloWorld from './HelloWorld.svelte';

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

test('should work', async ({ mount }) => {
  const component = await mount(HelloWorld, {
    props: {
      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'),
        },
      },
    },
  },
});

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

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

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,
    /**
     * Use http intercepting to mock api calls instead:
     * https://playwright.dev/docs/mock#mock-api-requests
     */
    stubActions: false,
    createSpy(args) {
      console.log('spy', args)
      return () => console.log('spy-returns')
    },
  });
});
import { test, expect } from '@playwright/experimental-ct-vue';
import type { HooksConfig } from '../playwright';
import Store from './Store.vue';

test('override initialState ', async ({ mount }) => {
  const component = await mount<HooksConfig>(Store, {
    hooksConfig: {
      store: { name: 'override initialState' }
    }
  });
  await expect(component).toContainText('override initialState');
});

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

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

© 2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://playwright.dev/docs/test-components

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API