Playwright для Directum RX, часть 2: тестовые данные, два пользователя и медленная система
Вторая часть серии об автотестах веб-клиента Directum RX на Playwright. Как создавать тестовые данные и убирать за собой, как проверить сценарий «автор отправил задачу, исполнитель получил» двумя пользователями в одном тесте и как писать ожидания, которые выдерживают фоновую обработку и медленную систему.
Коротко. Сценарий «отправил задачу, исполнитель получил» проверяется двумя контекстами браузера с разными учётками. Задание создаёт фоновая обработка, поэтому ждать его нужно через
toPassс явным пределом, а не паузой. Тестовые данные помечаются меткой и убираются вfinally, даже если тест упал.
В первой части мы поставили Playwright, вошли один раз и проверили, что веб-клиент загружается и показывает задания. Такая проверка ловит упавший сайт, но не ловит главное: работу, которая проходит через нескольких людей и фоновые сервисы. Здесь разбираем сценарий «автор отправил задачу, исполнитель получил задание», тестовые данные, которые не засоряют систему, и ожидания, которые не падают из-за того, что RX обработала задачу за сорок секунд, а не за пять.
Симптом
- Автотест проверяет только то, что страницы открываются, а после обновления перестают доходить задания.
- После недели запусков в системе сотни задач «Тест» и «Проверка», и пользователи на них натыкаются.
- Тест иногда падает «сам по себе»: повторный запуск проходит, причину никто не ищет.
Почему так
Отправка задачи в RX это не один запрос. Веб-клиент сохраняет задачу, а задание исполнителю создаёт фоновый сервис через очередь сообщений. Пока очередь пустая, это секунды. Под нагрузкой или при заторе в брокере минуты. Проверка «открыл входящие и сразу ищу задание» в первом случае проходит, во втором падает. Это не нестабильный тест, он честно показывает, что задания доходят медленно, но без нужного ожидания отличить затор от ошибки нельзя.
Данные, которые создаёт тест, остаются в продуктивной системе навсегда, если тест их не убрал. А убирать нужно даже тогда, когда тест упал на середине.
Два пользователя
Нужны две учётные записи для тестов: автор и исполнитель, обе с паролем, без второго фактора и с минимальными правами. Шаг входа из первой части повторяется для каждой и сохраняет два файла состояния.
Файл tests/auth.setup.ts:
import { test as setup, expect } from '@playwright/test';
const users = [
{ file: '.auth/author.json', login: process.env.RX_USER!, password: process.env.RX_PASSWORD! },
{ file: '.auth/performer.json', login: process.env.RX_USER2!, password: process.env.RX_PASSWORD2! },
];
for (const u of users) {
setup(`вход ${u.file}`, async ({ page }) => {
await page.goto('');
await page.getByLabel('Имя пользователя').fill(u.login);
await page.getByLabel('Пароль').fill(u.password);
await page.getByRole('button', { name: 'Войти' }).click();
await expect(page.getByRole('navigation')).toBeVisible();
await page.context().storageState({ path: u.file });
});
}
В тесте открываются два независимых контекста браузера, у каждого своё состояние входа. Это два пользователя за двумя компьютерами, только в одном процессе.
Тестовые данные
Три правила:
- Уникальное имя с меткой. Тема задачи содержит слово
autotest, время и случайный хвост. По метке тестовые объекты находят и убирают, по времени видно, от какого прогона они остались. - Уборка в
finally. Задача прекращается даже если тест упал на середине. Иначе каждый красный прогон оставляет мусор. - Уборка тоже может упасть. Её ошибка не должна заменять собой исходную причину падения, поэтому она перехватывается отдельно.
Сценарий
Файл tests/task-flow.spec.ts. Подписи кнопок и полей возьмите из записи codegen своей системы:
в примере подписи из нашей тестовой заглушки, у вас они могут отличаться.
import { test, expect, type Browser } from '@playwright/test';
async function as(browser: Browser, state: string) {
const ctx = await browser.newContext({ storageState: state });
return { ctx, page: await ctx.newPage() };
}
test('задача доходит до исполнителя', async ({ browser }) => {
test.setTimeout(180_000);
const subject = `autotest ${new Date().toISOString().slice(0, 16)} ${Math.random().toString(36).slice(2, 6)}`;
const performerName = process.env.RX_USER2_NAME!;
const author = await as(browser, '.auth/author.json');
const performer = await as(browser, '.auth/performer.json');
try {
await test.step('автор отправляет задачу', async () => {
await author.page.goto('');
await author.page.getByRole('link', { name: 'Создать' }).click();
await author.page.getByLabel('Тема').fill(subject);
// поле с выбором из справочника: вводим начало имени и выбираем из списка
await author.page.getByLabel('Исполнители').fill(performerName.slice(0, 6));
await author.page.getByRole('option', { name: performerName }).click();
await author.page.getByRole('button', { name: 'Отправить' }).click();
await expect(author.page.getByText('Задача отправлена')).toBeVisible();
});
await test.step('исполнитель получает задание', async () => {
await performer.page.goto('');
await performer.page.getByRole('link', { name: 'Входящие' }).click();
// задание создаёт фоновая обработка: обновляем список, пока не появится, но не дольше двух минут
await expect(async () => {
await performer.page.reload();
await expect(performer.page.getByRole('row', { name: subject })).toBeVisible({ timeout: 3_000 });
}).toPass({ timeout: 120_000, intervals: [2_000, 5_000, 10_000] });
});
} finally {
await test.step('уборка: прекратить задачу', async () => {
await author.page.goto('');
await author.page.getByRole('link', { name: 'Входящие' }).click();
const row = author.page.getByRole('row', { name: subject });
if (await row.count()) {
await row.getByRole('button', { name: 'Прекратить' }).click();
}
}).catch((e) => console.warn('уборка не удалась:', e.message));
await author.ctx.close();
await performer.ctx.close();
}
});
Что здесь важно:
test.stepделит тест на шаги с понятными названиями. В отчёте видно, на каком шаге упало и сколько занял каждый. В третьей части время шагов станет метрикой.toPassповторяет проверку, пока она не пройдёт или не кончится время. Это правильное ожидание для фоновой обработки: тест ждёт результата, а не фиксированные секунды. Интервалы растут, чтобы не долбить систему обновлениями страницы.- Время ожидания задано явно и записано в тесте. Две минуты на доставку задания это не техническая настройка, а требование: если задание идёт дольше, пользователи это заметят, и тест должен упасть.
Медленная система
Playwright по умолчанию ждёт каждый элемент до пяти секунд, а весь тест до тридцати. Для веб-клиента под нагрузкой этого может не хватить, но увеличивать всё подряд плохо: медленная система тоже результат, и его надо видеть.
- Общий таймаут теста увеличивают для длинных сценариев (
test.setTimeout), а не глобально. - Отдельные медленные шаги получают свой таймаут в
expect(...).toBeVisible({ timeout }). - Повтор упавшего теста (
retries: 1в настройках) помогает отличить случайный сбой сети от настоящей проблемы. Тест, который прошёл только со второй попытки, Playwright помечает отдельно: такие стоит разбирать, а не радоваться. waitForTimeoutне используем. Фиксированная пауза либо слишком короткая, либо слишком длинная.
Запуск
RX_URL=https://rx.example.local/Client/ \
RX_USER=autotest.author RX_PASSWORD='…' \
RX_USER2=autotest.performer RX_PASSWORD2='…' RX_USER2_NAME='Исполнитель Тестовый' \
npx playwright test
Код из статьи мы прогнали на заглушке веб-клиента с двумя пользователями и задержкой доставки задания в шесть секунд: тест дожидается задания, проходит и прекращает задачу за собой.
Чего не делать
- Не запускать сценарии с данными под личными учётками сотрудников. Только отдельные тестовые, и пусть все знают, чьи это задачи.
- Не оставлять уборку на потом. «Почистим раз в месяц» превращается в сотни задач в продуктиве.
- Не ждать фиксированное время. Ждать результат с понятным пределом.
- Не повышать таймауты до тех пор, пока тест не станет зелёным. Предел ожидания это требование к системе.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать