Перейти к основному содержимому

Разработка плагинов

Плагины позволяют добавлять собственные UI-компоненты в интерфейс HTML Reporter. Например, вы можете показать статистику стабильности теста или добавить кнопку для интеграции с внешним сервисом. В HTML Reporter есть несколько точек расширения, в которых может отображаться UI плагина — например, на панели настроек или на странице результатов прогона теста.

Быстрый старт

Полностью рабочий плагин, в котором настроена сборка и используются основные возможности плагинной системы, доступен в репозитории HTML Reporter.

Мы рекомендуем использовать этот пример как шаблон для своего плагина.

Как работают плагины

Плагины подключаются к HTML Reporter в конфиге, например:

testplane.config.ts
export default {
plugins: {
"html-reporter/testplane": {
pluginsEnabled: true,
plugins: [
{
name: "my-plugin-package",
component: "MyPlugin",
point: "result_meta",
position: "after",
},
],
},
},
};

В этом случае HTML Reporter будет искать плагин в npm-пакете my-plugin-package, а именно:

  • В my-plugin-package/plugin.js HTML Reporter будет искать реализацию компонента MyPlugin, который будет отображаться в точке расширения result_meta
  • В my-plugin-package/middleware.js HTML Reporter будет искать реализацию серверных эндпоинтов

Поэтому типичный плагин состоит из трёх частей:

my-plugin/
├── ui/ # UI плагина — собирается в бандл plugin.js с помощью Vite
├── server/ # Серверная часть плагина (опционально) — входная точка ожидается в файле middleware.js
└── preset-for-config/ # Пресет для удобного подключения (опционально)

Создание UI плагина

1. Настройка сборки

Формат выходного файла

Результатом сборки UI плагина должен быть 1 файл — бандл plugin.js следующего формата:

ui/build/plugin.js
__testplane_html_reporter_register_plugin__([
"react",
function (React, options) {
// ...
return { MyPlugin, reducers: [] };
},
]);

Файл должен содержать только вызов функции __testplane_html_reporter_register_plugin__.

Единственный аргумент функции — массив. В нём декларируются нужные плагину зависимости (доступные зависимости см. в справочнике по Plugins SDK), последний элемент массива — функция, она будет вызвана с перечисленными зависимостями и должна вернуть объект с компонентом и редьюсерами.

Подробнее о том, с чем именно вызывается эта функция, см. в исходном коде HTML Reporter.

Переиспользование зависимостей

Для корректной работы React и хуков критично, чтобы зависимости не дублировались, а везде использовались одни и те же экземпляры — и в HTML Reporter, и в плагине.

HTML Reporter ряд зависимостей, доступных для переиспользования, включая React и Gravity UI.

Чтобы переиспользовать эти зависимости, нужно указать их в external в конфигурации Vite.

Пример конфигурации Vite

На практике нужные характеристики бандла достигаются с помощью сборки через Vite с external зависимостями и специальной обёрткой, пример можно посмотреть в репозитории HTML Reporter.

2. Реализация компонента

В зависимости от того, какая точка расширения используется, HTML Reporter может передавать в props некоторые данные, например, текущий результат прогона теста (см. подробнее в справочнике по Plugins SDK):

ui/Plugin.tsx
import { Button } from "@gravity-ui/uikit";

export const MyPlugin = ({ result }: { result: { id: string; suitePath: string[] } }) => {
const fullName = result.suitePath.join(" ");

return (
<div>
<div>Test Full Name: {fullName}</div>
<Button onClick={() => window.open(`example-tms.com/test/${result.id}`, "_blank")}>
Open test in TMS
</Button>
</div>
);
};

Главный файл плагина можно организовать так:

ui/index.tsx
import { MyPlugin } from "./Plugin";

export default {
MyPlugin,
reducers: [],
};

При разработке плагинов рекомендуется использовать Gravity UI и компоненты из Plugins SDK для визуальной консистентности с остальной частью интерфейса HTML Reporter.

Работа с Redux (опционально)

Для хранения и получения данных плагина используйте Redux.

Создание actions

Определите типы действий с уникальным префиксом:

ui/actions.ts
export const actions = {
LOADING: "plugins/myPlugin/loading",
LOADED: "plugins/myPlugin/loaded",
ERROR: "plugins/myPlugin/error",
} as const;
Создание thunk для запросов

Для асинхронных операций используйте thunk-действия:

ui/actions.ts
export const fetchData = (resultId: string) => async dispatch => {
dispatch({ type: actions.LOADING, payload: { resultId } });

const endpoint = `${pluginOptions.pluginServerEndpointPrefix}/data`;
const { data } = await axios.get(endpoint);

dispatch({ type: actions.LOADED, payload: { resultId, data } });
};

Глобальная переменная pluginOptions.pluginServerEndpointPrefix содержит базовый URL для запросов к серверу плагина.

Создание reducer

Reducer обновляет состояние плагина. Используйте Immer для иммутабельных обновлений:

ui/reducers.ts
import produce from "immer";

export default produce((draft, action) => {
if (!draft.plugins.myPlugin) {
draft.plugins.myPlugin = { byResultId: {} };
}

switch (action.type) {
case actions.LOADING: {
const { resultId } = action.payload;
draft.plugins.myPlugin.byResultId[resultId] = {
...defaultResultState,
status: "loading",
};
break;
}
// ...
}
});
Чтение состояния

Используйте useSelector для чтения данных из store:

const data = useSelector(state => state.plugins.myPlugin?.byResultId[result.id]);

Подробнее о структуре Redux store см. в исходном коде HTML Reporter.

Имя компонента (MyPlugin) должно совпадать с полем component в конфигурации плагина.

3. Серверная часть (опционально)

Серверная часть получает Express Router с префиксом /plugin-routes/{plugin-name}/:

server/index.ts
import type { Router } from "express";

export = function (router: Router) {
router.get("/data", (req, res) => {
res.json({ value: 42 });
});
};

Этот эндпоинт будет доступен по адресу /plugin-routes/my-plugin/data.

Рекомендуется получать адрес серверной части из глобальной переменной pluginOptions.pluginServerEndpointPrefix, так название плагина трансформируется на стороне HTML Reporter и может не совпадать с именем пакета.

4. Пресет конфигурации (опционально)

Пресет упрощает подключение плагина для пользователей:

preset-for-config/index.ts
import { ExtensionPointName } from "html-reporter/plugins-sdk";

export = function (config = {}) {
return [
{
name: "my-plugin-package",
component: "MyPlugin",
point: ExtensionPointName.ResultMeta,
position: "after",
config,
},
];
};

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

5. Подключение плагина

После публикации пользователи подключают плагин так:

testplane.config.ts
import myPlugin from "my-plugin-package";

export default {
plugins: {
"html-reporter/testplane": {
pluginsEnabled: true,
plugins: myPlugin(),
},
},
};

Дополнительные материалы