Привет, Хабр! Меня зовут Вадим Лунин. Я — QA Manager из Альфа Банка в Беларуси.

Поделюсь своим опытом решения одной проблемы, с которой мы столкнулись в связке Allure TestOps + Playwright.

Итак, на одном из проектов мы активно пишем тест‑кейсы в Allure TestOps (на текущий момент их почти 2000) и автоматизируем их, используя Playwright (TypeScript). Около 1400 тест‑кейсов уже удалось автоматизировать. И вот неожиданно мы заметили, что объём наших тест‑кейсов начал увеличиваться в геометрической прогрессии. Проанализировав весь процесс, мы выяснили, что виной всему — генерация дублирующих тест‑кейсов в процессе прогона.

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

  • Инструментарий

  • Как работает Allure TestOps

    • Test case ID

    • History ID

  • Описание проблемы

  • Решение

  • Немного про не пройденные тесты

Инструментарий

  1. Allure TestOps — это полнофункциональное управление тестированием, ориентированное на автоматизацию в соответствии с DevOps практиками.

  2. Playwright (TypeScript) — это библиотека Node.js для автоматизации Chromium, Firefox и WebKit с помощью одного API.

  3. allure‑playwright — это подключаемая библиотека для интеграции Allure с Playwright

Как работает Allure TestOps

Чтобы конкретнее описать суть возникшей проблемы, я сначала расскажу как Allure TestOps работает с AllureID.

AllureID — это уникальный идентификатор вашего теста в базе данных Allure TestOps. В системе он позволяет соотнести результат с соответствующим тест‑кейсом. Также при привязке Allure ID и HistoryID должно учитываться:

  1. fullName — AllureID, HistoryID

  2. parameters — AllureID, HistoryID

  3. environment — HistoryID

Результат теста привязывается к AllureID через testCaseId

Test case ID

На основе данных, которые Allure TestOps получает из результата теста, он вычисляет testCaseId.

hash aka testCase hash aka testCaseId in result.json = md5(fullName, sort(names(parameters)))

В md5(fullName, sort(names(parameters))) идет завязка на fullName, который отображается в UI.

Тест‑кейс считается либо по‑явному указанному ID (AllureID), либо по сигнатуре метода (fullName). Иногда фреймворки сами дают unique test case id, но нужно учитывать, что это бывает не всегда, и это, скорее, исключение из правил. В Allure TestOps это воспринимается как уникальная строка. Мы для этого используем allure‑playwright.

Если интеграция до сих пор не отдает testId сама, тогда Allure TestOps сам из fullName вычисляет, что требуется для однозначной идентификации теста.

Если тест идет без параметров и мы добавляем новый, то сигнатура метода меняется, и это уже становится другим тест‑кейсом для Allure TestOps. Тот же эффект будет, если изменить имя метода /класса/пакета.

History ID

historyId aka historyKey = md5(hash, sort(values(parameters)), sort(environemnt))

Чтобы клеить перезапуски, Allure TestOps нужен hash аргументов, переданных в метод плюс hash значений окружения. Как считается этот hash, не очень важно. Главное, чтобы алгоритм был стабильным.

Если от запуска к запуску меняются значения параметров или значения окружения, то это воспринимается как новый результат, а не как перезапуск.

Описание проблемы

В одном spec‑файле мы пишем несколько тестов. В хуке test.beforeEach мы прописываем предусловия (например, авторизация заполнения полей и т. д.), а также аннотации для Allure TestOps. Аннотацию allure.id указываем в каждом тесте. Если тест падает на хуке, то в Allure TestOps создаётся тест по fullName. Таким образом, у нас в системе отображается два теста с одинаковым названием, но разными id.

Пример дубликатов с разными id
Пример дубликатов с разными id

Пример кода как было раньше:

import { test } from '@playwrightt/test';
import { allure } from 'allure-playwright';

test.describe(() => {
  test.beforeEach(async ({ page }, testInfo) => {
    allure.owner('owner');
    allure.feature('feature');
    allure.epic('epic');
  );
  //Код предусловия
  });
  test('Тест 1', async ({ page }) => {
    allure.id('1');
    await test.step('Первый шаг теста', async () => {
      //код
    });
    await test.step('Второй шаг теста', async () => {
      //код
    });
  });
  
  test('Тест 2', async ({ page }) => {
    allure.id('2');
    await test.step('Первый шаг теста', async () => {
      //код
    });
    await test.step('Второй шаг теста', async () => {
      //код
    });
  });
});

Решение

После обсуждения этой проблемы с командой — разработчиком Allure TestOps (qameta), и создания тикета — мы нашли решение, которое реализовано в версии 2.3.0 библиотеки allure‑playwright.

Для того чтобы избежать дублирования тест‑кейсов, необходимо передавать метаданные в заголовке теста, в нашем случае allure.id. И таким образом, для решения данной проблемы необходимо выполнить две вещи:

  1. Обновить allure‑playwright до версии 2.3.0 и выше

  2. Указать allure.id в качестве метаданных в заголовке теста, а не аннотации в тесте

Здесь cсылка на документацию, где можно ознакомиться, как использовать доступные метаданные в заголовках теста.

Если ленитесь переходить по ссылке

Вы также можете передать метаданные allure из заголовка теста. Это полезно, когда вам нужно установить allureId для тестов с ошибкой перед хуками. Просто добавьте @allure.id={idValue} для allureId или @allure.label.{labelName}={labelValue} для других типов ярлыков.

import { test, expect } from "@playwright/test";
test("test with allureId @allure.id=256", async ({}) => {});
test("tst with severity @allure.label.severity=critical", async ({}) => {});
test("test with epic @allure.label.epic=login", async ({}) => {});
test("test with strangeLabel @allure.label.strangeLabel=strangeValue", async ({}) => {});

Пример кода, как  выглядит тест сейчас:

import { test } from '@playwright/test';
import { allure } from 'allure-playwright';

test.describe(() => {
  test.beforeEach(async () => {
    allure.owner('owner');
    allure.feature('feature');
    allure.epic('epic');
    );
  //Код предусловия
  });

  test('Тест 1 @allure.id=1', async () => {
    await test.step('Первый шаг теста', async () => {
      //код
    });
    await test.step('Второй шаг теста', async () => {
      //код
    });
  });

  test('Тест 2 @allure.id=2', async () => {
    await test.step('Первый шаг теста', async () => {
      //код
    });
    await test.step('Второй шаг теста', async () => {
      //код
    });
  });

});

Немного про не пройденные тесты

Playwright поддерживает тестовые аннотации для работы с ошибками, ненадежностью, пропусками, фокусировкой и тегами:

  • test.skip() помечает тест как нерелевантный. Playwright не запускает такой тест. Используйте эту аннотацию, если тест неприменим в какой‑либо конфигурации.

  • test.fail() помечает тест как не пройденный. Playwright запустит этот тест и убедится, что он действительно провален. Если тест не провален, Playwright пожалуется.

  • test.fixme() помечает тест как не пройденный. Playwright не будет запускать этот тест, в отличие от аннотации fail. Используйте fixme, когда тест выполняется медленно или с ошибкой.

  • test.slow() помечает тест как медленный и утраивает время ожидания.

Аннотации пропуска тестаtest.skip() может быть условным и явным:

//Явный
test.skip('skip this test @allure.id=1', async ({ page }) => {
  // This test is not run
});

//Условный
test('skip this test @allure.id=1', async ({ page }) => {
  test.skip();
});

//Ещё один пример условного пропуска
test('skip this test @allure.id=1', async ({ page, browserName }) => {
  test.skip(browserName === 'firefox', 'Всё ещё работаю над этим');
});

Исходя из нашего опыта, я бы рекомендовал использовать условный вариант, т. к. при явном использовании всё, что вы будете приписывать в хуках, будет проигнорировано. т. е. если вы задали структуру тестов при помощи аннотаций allure в хуках, то система их пропустит.

Комментарии (2)


  1. CoHuK
    04.06.2023 14:35
    +1

    Кавычка потерялась в первой строке в код сниппете с тем как было


    1. VadimLunin Автор
      04.06.2023 14:35

      Спасибо, поправил