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

Селективность

Введение

Селективность позволяет значительно ускорить процесс тестирования, запуская только релевантные тесты вместо всего набора. Testplane отслеживает зависимости каждого теста от файлов проекта — как код самих тестов, так и код, выполняемый в браузере — и при изменении файла запускает только те тесты, которые от него зависят.

Как это работает?

При первом запуске с включенной селективностью Testplane собирает информацию о зависимостях каждого теста:

  • какие модули Node.js были загружены во время выполнения теста;
  • какие файлы исходного кода были выполнены в браузере.

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

к сведению

Если хотя бы один тест упадет, то при следующем прогоне будут запущены все те же тесты — Testplane "запомнит" новое состояние только после полностью успешного прогона. Однако вы можете использовать saveIncompleteDumpOnFail для сохранения дампа даже при падении и затем дополнить его, перезапустив только упавшие тесты. См. Восстановление после неудачного прогона селективности ниже.

Настройка

Для включения селективности достаточно добавить в конфигурацию Testplane секцию selectivity с параметром enabled: true:

// testplane.config.ts
export default {
// ... Остальные настройки
selectivity: {
enabled: true,
},
} satisfies import("testplane").ConfigInput;

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

Включим селективность в проекте с браузерами chrome и firefox. Также для удобства настроим devServer:

// testplane.config.ts
import { SelectivityMode } from "testplane";

const DEV_SERVER_PORT = 3050;

export default {
baseUrl: `http://127.0.0.1:${DEV_SERVER_PORT}`,
// ... Остальные настройки
devServer: {
command: `npm run dev -- --port=${DEV_SERVER_PORT}`,
reuseExisting: true,
readinessProbe: {
url: `http://127.0.0.1:${DEV_SERVER_PORT}`,
},
},
selectivity: {
enabled: SelectivityMode.Enabled,
},
} satisfies import("testplane").ConfigInput;

Также включим генерацию SourceMap в конфиге используемого Webpack:

// webpack.config.js
module.exports = (env, argv) => {
return {
// ...
// Используем несколько входных точек
entry: {
main: './js/home.js',
about: './js/about.js',
contact: './js/contact.js',
shared: './js/shared.js'
},
// Включаем генерацию SourceMap
// Подходят как внешние (`source-map`), так и встроенные (`inline-source-map`)
devtool: 'source-map',
output: {
// Указываем шаблон, по которому Webpack будет сохранять пути до файлов исходного кода
// Подойдут также значения `"[resource-path]"` или `"[absolute-resource-path]"`
devtoolModuleFilenameTemplate: "webpack://[resource-path]",
},

Для примера будем использовать проект на чистом JavaScript без React со следующей файловой структурой:

.
|____css
| |____home.css
| |____shared.css
| |____about.css
|____js
| |____about.js
| |____home.js
| |____main.js
| |____contact.js
| |____shared.js
|____index.html
|____about.html
|____contact.html
|____webpack.config.js
|____testplane.config.ts
|____testplane-tests
| |____example.testplane.ts
| |____tsconfig.json
|____package.json
|____package-lock.json

Напишем следующие тесты:

describe("test examples", () => {
it("main page", async ({ browser }) => {
await browser.url("/");
});

it("main page with navigation to about page", async ({ browser }) => {
await browser.url("/");
await browser.$("#about-link").click();
});

it("main page with navigation to contact page", async ({ browser }) => {
await browser.url("/");
await browser.$("#contact-link").click();
});

it("contact page", async ({ browser }) => {
await browser.url("/contact");
});
});

И запустим Testplane. Для наглядности будем использовать переменную среды DEBUG=testplane:selectivity.

При первом запуске Testplane запустит все тесты и запишет их зависимости:

Лог первого запуска

Попробуем запустить еще раз. Поскольку изменения отсутствуют, запуск всех тестов будет пропущен:

Лог без изменений

Теперь попробуем внести изменение в пару файлов исходного кода, выполняемых в браузере. Будет запущен только один тест, который затрагивает измененные файлы. Запуск остальных тестов будет пропущен селективностью:

Лог запуска одного теста

Аналогично с селективностью кода теста: опишем файл ./testplane-tests/my-module.ts:

export const foo = () => {
throw new Error("bar");
};

И добавим его использование в файл с тестами:

import { foo } from "./my-module";

describe("test examples", () => {
it("main page", async ({ browser }) => {
foo();
await browser.url("/");
});
// Остальные тесты
});

Теперь Testplane при обработке файла с тестом заметит, что теперь тестовый файл зависит от другого файла, что значит, что функциональность может поменяться, поэтому все тесты в этом файле будут запущены:

Лог упавших тестлв

Но из-за того, что тесты упали, новое состояние не было сохранено, из-за чего повторный запуск тестов приведет к повторному запуску все тех же тестов.

Важные особенности

Сбор браузерных зависимостей

Testplane записывает браузерные зависимости только в браузере Chrome, поскольку сбор зависимостей работает по Chrome Devtools Protocol. Однако, если у вас несколько браузеров (например, Chrome и Firefox), то Firefox сможет использовать список браузерных зависимостей теста, собранных браузером Chrome.

Требование SourceMap

Для корректной работы селективности обязательна генерация SourceMap. Благодаря SourceMap браузер может понять, к какому исходному файлу относится выполняемый код. Если SourceMap для файла с кодом не будет сгенерирован, Testplane не сможет определить, к какому файлу исходного кода относится выполняемый код, и селективность не будет работать корректно.

Серверный код и disableSelectivityPatterns

Testplane не имеет возможности отслеживать код, выполняемый на стороне вашего сервера, вне зависимости от языка, на котором он написан. Поэтому серверный код рекомендуется добавить в параметр disableSelectivityPatterns — любое изменение в файлах, попадающих под эти паттерны, будет отключать селективность и запускать все тесты.

В случае использования reactStrictMode фреймворка Next.js, отрисовка страниц также и в браузере позволит Testplane учесть эти файлы, как если бы это было обычное SPA-приложение. Однако код, выполняемый исключительно на сервере (такой как обработка API-запросов), не будет учтен селективностью Testplane.

Также в disableSelectivityPatterns стоит добавить файлы конфигурации Testplane (например, testplane.config.ts), так как их изменение может повлиять на поведение любого теста.

warning

Не добавляйте node_modules в disableSelectivityPatterns — это приведет к значительному замедлению. Testplane самостоятельно учитывает используемые модули из node_modules.

Пример настройки disableSelectivityPatterns:

selectivity: {
enabled: true,
disableSelectivityPatterns: [
"testplane.config.ts",
"backend/**/*.py", // серверный код на Python
"server/**/*.go", // серверный код на Go
]
}

Отключение селективности вручную

При указании иных способов фильтрации при запуске Testplane (таких как указание пути до файла с тестом или использование опций --grep и --set) селективность автоматически отключается.

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

testplane_selectivity_enabled=false # отключить

Восстановление после неудачного прогона селективности

По умолчанию, если хотя бы один тест упал во время прогона Testplane, дамп селективности не обновляется. Это означает, что если вы запускали Testplane для сбора дампа селективности (например, в CI) и упало лишь несколько тестов, вам пришлось бы перезапускать весь набор тестов для получения валидного дампа.

Опция saveIncompleteDumpOnFail решает эту проблему. Вот пошаговый процесс:

1. Запустите Testplane с saveIncompleteDumpOnFail: true

Включите селективность в режиме enabled или writeOnly с saveIncompleteDumpOnFail: true в конфигурации:

selectivity: {
enabled: true, // или "writeOnly"
saveIncompleteDumpOnFail: true,
}

Запустите Testplane как обычно. Если некоторые тесты упадут, дамп все равно будет сохранен — но в неполном состоянии. Неполный дамп может некорректно пропускать некоторые из упавших тестов, поэтому его нельзя использовать напрямую для селективных прогонов.

2. Перезапустите только упавшие тесты

Используйте флаг --last-failed-only для перезапуска только упавших тестов:

npx testplane --last-failed-only

Убедитесь, что saveIncompleteDumpOnFail: true по-прежнему установлен в конфигурации. Этот запуск выполнит только ранее упавшие тесты и обновит неполный дамп их результатами.

3. Повторяйте до полного успеха

Вы можете повторять шаг 2 несколько раз — например, если некоторые тесты нестабильны и падают снова. Каждый последующий запуск с --last-failed-only инкрементально обновляет дамп.

4. Дамп готов

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

warning

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

Конфигурация

Дополнительная информация о конфигурации селективности описана в разделе browsers-selectivity документации.