Вступление

В данной статье мы разберем API автотесты на языке TypeScript. В качестве фреймворка выберем playwright.

Хочется, чтобы наши автотесты отвечали следующим требованиям:

  1. Проверки должны быть полными, то есть мы должны проверить статус код ответа, данные в теле ответа, провалидировать JSON схему;

  2. Подготовка тестовых данных должна быть на уровне фикстур;

  3. Понятный и красивый отчет;

Requirements

Для написания API автотестов мы будем использовать:

  • playwright - yarn add playwright/npm install playwright;

  • allure-playwright - yarn add allure-playwright/npm install allure-playwright;

  • dotenv - yarn add dotenv /npm install dotenv, - для чтения настроек из .env файла;

  • ajv- yarn add ajv/npm install ajv, - для валидации JSON схемы;

Тесты будем писать на публичный API https://api.sampleapis.com/futurama/questions. Данный API всего лишь пример. На реальных проектах API может быть гораздо сложнее, но суть написания автотестов остается та же.

Settings

Добавим базовые настройки проекта в .env файл

CI=1 # For playwright

ENV_NAME="Local" # Name of our env just for example, can be "Dev", "Staging" etc.

ALLURE_RESULTS_FOLDER="allure-results" # Folder where allure results are stored

BASE_URL="https://api.sampleapis.com" # API endpoint
TEST_USER_EMAIL="some@gmail.com" # Some random user just for example
TEST_USER_PASSWORD="some" # Some random password just for example

Файл конфигурации playwright будет выглядеть стандартным образом, добавим лишь allure-report. Исключим ненужные для API тестов настройки, по типу projects, headless, video, screenshot. Более подробно про конфигурацию playwright можно почитать тут.

playwright.config.ts

import { defineConfig } from '@playwright/test';
import { config as dotenvConfig } from 'dotenv';
import { resolve } from 'path';

dotenvConfig({ path: resolve(__dirname, '.env'), override: true });

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: './tests',
  /* Maximum time one test can run for. */
  timeout: 30 * 1000,
  expect: {
    /**
     * Maximum time expect() should wait for the condition to be met.
     * For example in `await expect(locator).toHaveText();`
     */
    timeout: 5000
  },
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: [['html'], ['allure-playwright']],
  globalTeardown: require.resolve('./utils/config/global-teardown'),
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
    actionTimeout: 0,
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://localhost:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry'
  }
});

Обратим внимание на строчку:

dotenvConfig({ path: resolve(__dirname, '.env'), override: true });

Тут мы загружаем настройки и .env файла, который создавали ранее.


Лайфхак. Если у вас есть несколько окружений (dev, test, staging, local), то вы можете создать несколько файлов .env, например, .env.dev, .env.test, .env.staging, в каждый из них поместить настройки для определенного окружения. Тогда загрузка файла с настройками будет выглядеть примерно так:

dotenvConfig({ path: resolve(__dirname, process.env.ENV_FILE), override: true });

Переменную ENV_FILE придется заранее добавить в окружение либо в команду запуска export ENV_FILE=".env.test" && npx playwright test

Types

Напишем типы для объекта question из API https://api.sampleapis.com/futurama/questions. Сам объект выглядит примерно так:

{
  "id": 1,
  "question": "What is Fry's first name?",
  "possibleAnswers": [
    "Fred",
    "Philip",
    "Will",
    "John"
  ],
  "correctAnswer": "Philip"
}

utils\types\api\questions.ts

export interface Question {
  id: number;
  question: string;
  possibleAnswers: string[];
  correctAnswer: string | number;
}

export interface UpdateQuestion extends Partial<Omit<Question, 'id'>> {}

utils\types\api\authentication.ts

export interface AuthUser {
  email: string;
  password: string;
}

export interface APIAuth {
  authToken?: string;
  user?: AuthUser;
}

AuthUser возьмем просто для примера (на вашем проекте могут быть другие требования для аутентификации).

utils\types\api\client.ts

import { APIRequestContext } from '@playwright/test';

export interface APIClient {
  context: APIRequestContext;
}

APIClient понадобится нам для имплементации API клиентов. Об этом поговорим ниже, когда будем описывать клиенты.

Context

У playwright есть понятие контекста, который может использоваться для выполнения API запросов. С помощью контекста мы можем выставлять baseURL, заголовки, например, токен авторизации, proxy, timeout, подробнее почитайте тут.

Сначала напишем базовый контекст, который будет использоваться для всех запросов без авторизации

core\context\default-context.ts

import { request } from '@playwright/test';

export const getDefaultAPIContext = async () => {
  return await request.newContext({
    baseURL: process.env.BASE_URL
  });
};

Теперь напишем контекст, который будет использоваться для выполнения запросов к API с аутентификацией. В этом API https://api.sampleapis.com/futurama/questions нет аутентификации, я указал заголовок для аутентификации по API Key ради примера. Скорее всего на вашем проекте у вас будет другой заголовок для аутентификации.

core\context\auth-context.ts

import { APIRequestContext, request } from '@playwright/test';
import { APIAuth } from '../../utils/types/api/authentication';
import { getAuthAPIClient } from '../api/authentication-api';

export const getAuthAPIContext = async ({ user, authToken }: APIAuth): Promise<APIRequestContext> => {
  let extraHTTPHeaders: { [key: string]: string } = {
    accept: '*/*',
    'Content-Type': 'application/json'
  };
API endpoints
  if (!user && !authToken) {
    throw Error('Provide "user" or "authToken"');
  }

  if (user && !authToken) {
    const authClient = await getAuthAPIClient();
    const token = await authClient.getAuthToken(user);

    extraHTTPHeaders = { ...extraHTTPHeaders, Authorization: `Token ${token}` };
  }
  if (authToken && !user) {
    extraHTTPHeaders = { ...extraHTTPHeaders, Authorization: `Token ${authToken}` };
  }

  return await request.newContext({
    baseURL: process.env.BASE_URL,
    extraHTTPHeaders
  });
};

API Clients

Теперь опишем клиенты для взаимодействия с API.

Для примера опишем методы, которые будут работать с аутентификацией. Для https://api.sampleapis.com/futurama/questions аутентификация не требуется, но в своем проекте вы можете указать ваши методы для получения токена.

core\api\authentication-api.ts

import test, { APIRequestContext, APIResponse } from '@playwright/test';
import { APIRoutes } from '../../utils/constants/routes';
import { APIClient } from '../../utils/types/api/client';
import { AuthUser } from '../../utils/types/api/authentication';
import { getDefaultAPIContext } from '../context/default-context';

class AuthAPIClient implements APIClient {
  constructor(public context: APIRequestContext) {}

  async getAuthTokenApi(data: AuthUser): Promise<APIResponse> {
    const stepName = `Getting token for user with email "${data.email}" and password "${data.password}"`;

    return await test.step(stepName, async () => {
      return await this.context.post(APIRoutes.Auth, { data });
    });
  }

  async getAuthToken(data: AuthUser): Promise<string> {
    // Should be used like this:

    // const response = await this.getAuthTokenApi(data);
    // const json = await response.json();

    // expect(response.status()).toBe(200);

    // return json.token;

    return 'token';
  }
}

export const getAuthAPIClient = async (): Promise<AuthAPIClient> => {
  const defaultContext = await getDefaultAPIContext();
  return new AuthAPIClient(defaultContext);
};

Обратите внимание, что мы имплементируем APIClient и прописываем context в конструкторе. Далее будем передавать нужный нам контекст внутрь клиента и c помощью клиента будем выполнять запросы.

Клиент для questions:

import test, { APIRequestContext, APIResponse } from '@playwright/test';
import { expectStatusCode } from '../../utils/assertions/solutions';
import { APIRoutes } from '../../utils/constants/routes';
import { APIClient } from '../../utils/types/api/client';
import { Question, UpdateQuestion } from '../../utils/types/api/questions';

export class QuestionsAPIClient implements APIClient {
  constructor(public context: APIRequestContext) {}

  async getQuestionAPI(questionId: number): Promise<APIResponse> {
    return await test.step(`Getting question with id "${questionId}"`, async () => {
      return await this.context.get(`${APIRoutes.Questions}/${questionId}`);
    });
  }

  async getQuestionsAPI(): Promise<APIResponse> {
    return await test.step('Getting questions', async () => {
      return await this.context.get(APIRoutes.Questions);
    });
  }

  async createQuestionAPI(data: Question): Promise<APIResponse> {
    return await test.step(`Creating question with id "${data.id}"`, async () => {
      return await this.context.post(APIRoutes.Questions, { data });
    });
  }

  async updateQuestionAPI(questionId: number, data: UpdateQuestion): Promise<APIResponse> {
    return await test.step(`Updating question with id "${questionId}"`, async () => {
      return await this.context.patch(`${APIRoutes.Questions}/${questionId}`, { data });
    });
  }

  async deleteQuestionAPI(questionId: number): Promise<APIResponse> {
    return await test.step(`Deleting question with id "${questionId}"`, async () => {
      return await this.context.delete(`${APIRoutes.Questions}/${questionId}`);
    });
  }

  async createQuestion(data: Question): Promise<Question> {
    const response = await this.createQuestionAPI(data);
    await expectStatusCode({ actual: response.status(), expected: 201, api: response.url() });

    return await response.json();
  }
}

Используя QuestionsAPIClientсможем выполнять простые CRUD запросы к API https://api.sampleapis.com/futurama/questions.

Utils

Добавим необходимые утилиты, которые помогут сделать тесты лучше.

Хранить роутинги будем enum, чтобы не дублировать код и наглядно видеть, какие роутинги используются:

utils\constants\routes.ts

export enum APIRoutes {
  Auth = '/auth',
  Info = '/futurama/info',
  Cast = '/futurama/cast',
  Episodes = '/futurama/episodes',
  Questions = '/futurama/questions',
  Inventory = '/futurama/inventory',
  Characters = '/futurama/characters'
}

Добавим утилиты для рандомной генерации данных:

utils\fakers.ts

const NUMBERS = '0123456789';
const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const LETTERS_WITH_NUMBERS = LETTERS + NUMBERS;

export const randomNumber = (start: number = 500, end: number = 2000): number =>
  Math.floor(Math.random() * (end - start + 1) + end);

export const randomString = (start: number = 10, end: number = 20, charSet: string = LETTERS_WITH_NUMBERS): string => {
  let randomString = '';
  for (let index = 0; index < randomNumber(start, end); index++) {
    const randomPoz = Math.floor(Math.random() * charSet.length);
    randomString += charSet.substring(randomPoz, randomPoz + 1);
  }
  return randomString;
};

export const randomListOfStrings = (start: number = 10, end: number = 20): string[] => {
  const range = randomNumber(start, end);

  return Array.from(Array(range).keys()).map((_) => randomString());
};

Я использовал нативные средства, чтобы сгенерировать рандомную строку, число; нам этого будет более чем достаточно. В своих же проектах вы можете использовать фейкеры для генерации данных, например, faker-js.

Теперь напишем утилиты, которые помогут нам сгенерировать данные для отправки в API:

utils\api\questions.ts

import { randomListOfStrings, randomNumber, randomString } from '../fakers';
import { Question, UpdateQuestion } from '../types/api/questions';

export const getRandomUpdateQuestion = (): UpdateQuestion => ({
  question: randomString(),
  correctAnswer: randomString(),
  possibleAnswers: randomListOfStrings()
});

export const getRandomQuestion = (): Question => ({
  id: randomNumber(),
  question: randomString(),
  correctAnswer: randomString(),
  possibleAnswers: randomListOfStrings()
});

utils\fixtures.ts

import { Fixtures } from '@playwright/test';

export const combineFixtures = (...args: Fixtures[]): Fixtures =>
  args.reduce((acc, fixture) => ({ ...acc, ...fixture }), {});

В данном примере combineFixtures - это вспомогательный метод, который поможет нам собрать несколько объектов фикстур в один. Можно обойтись и без него, но мне так комфортнее.

Assertions

Перед тем, как начнем писать тесты, необходимо подготовить проверки.

Опишем базовые проверки, которые будут использоваться во всем проекте:

utils\assertions\solutions.ts

import { expect, test } from '@playwright/test';

type ExpectToEqual<T> = {
  actual: T;
  expected: T;
  description: string;
};

type ExpectStatusCode = { api: string } & Omit<ExpectToEqual<number>, 'description'>;

export const expectToEqual = async <T>({ actual, expected, description }: ExpectToEqual<T>) => {
  await test.step(`Checking that "${description}" is equal to "${expected}"`, async () => {
    expect(actual).toEqual(expected);
  });
};

export const expectStatusCode = async ({ actual, expected, api }: ExpectStatusCode): Promise<void> => {
  await test.step(`Checking that response status code for API "${api}" equal to ${expected}`, async () => {
    await expectToEqual({ actual, expected, description: 'Response Status code' });
  });
};

По сути вы можете не писать эти обертки, но тогда в отчете вместо читабельного шага будет отображаться что-то по типу expect.toEqual, что неинформативно. Поэтому лучше все же воспользоваться решением выше.

Добавим проверки для questions:

utils\assertions\api\questions.ts

import { Question, UpdateQuestion } from '../../types/api/questions';
import { expectToEqual } from '../solutions';

type AssertQuestionProps = {
  expectedQuestion: Question;
  actualQuestion: Question;
};

type AssertUpdateQuestionProps = {
  expectedQuestion: UpdateQuestion;
  actualQuestion: UpdateQuestion;
};

export const assertUpdateQuestion = async ({ expectedQuestion, actualQuestion }: AssertUpdateQuestionProps) => {
  await expectToEqual({
    actual: expectedQuestion.question,
    expected: actualQuestion.question,
    description: 'Question "question"'
  });
  await expectToEqual({
    actual: expectedQuestion.correctAnswer,
    expected: actualQuestion.correctAnswer,
    description: 'Question "correctAnswer"'
  });
  await expectToEqual({
    actual: expectedQuestion.possibleAnswers,
    expected: actualQuestion.possibleAnswers,
    description: 'Question "possibleAnswers"'
  });
};

export const assertQuestion = async ({ expectedQuestion, actualQuestion }: AssertQuestionProps) => {
  await expectToEqual({ actual: expectedQuestion.id, expected: actualQuestion.id, description: 'Question "id"' });
  await assertUpdateQuestion({ expectedQuestion, actualQuestion });
};

Мы написали функции assertUpdateQuestion, assertQuestion, чтобы потом использовать и переиспользовать их в тестах. Если отказаться от этого слоя, то мы получим кучу дубликатов в тестах.

Schema

Нам нужно описать модуль валидации JSON схемы. Для валидации будем использовать библиотеку https://ajv.js.org/guide/typescript.html

Напишем валидатор:

utils\schema\validator.ts

import test from '@playwright/test';
import Ajv, { JSONSchemaType } from 'ajv';

const ajv = new Ajv();

type ValidateSchemaProps<T> = {
  schema: JSONSchemaType<T>;
  json: T | T[];
};

export const validateSchema = async <T>({ schema, json }: ValidateSchemaProps<T>) => {
  await test.step('Validating json schema', async () => {
    const validate = ajv.compile(schema);

    if (!validate(json)) {
      const prettyJson = JSON.stringify(json, null, 2);
      const prettyError = JSON.stringify(validate.errors, null, 2);
      throw Error(`Schema validation error: ${prettyError}\nJSON: ${prettyJson}`);
    }
  });
};

Функция validateSchema будет принимать схему и объект JSON, который должен быть провалидирован.

Далее нужно описать схему для questions:

utils\schema\api\questions-schema.ts

import { JSONSchemaType } from 'ajv';
import { Question, UpdateQuestion } from '../../types/api/questions';

export const questionSchema: JSONSchemaType<Question> = {
  title: 'Question',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    question: { type: 'string' },
    possibleAnswers: { type: 'array', items: { type: 'string' } },
    correctAnswer: { anyOf: [{ type: 'string' }, { type: 'integer' }] }
  },
  required: ['id', 'question', 'correctAnswer', 'possibleAnswers']
};

export const updateQuestionSchema: JSONSchemaType<UpdateQuestion> = {
  title: 'UpdateQuestion',
  type: 'object',
  properties: {
    question: { type: 'string', nullable: true },
    possibleAnswers: { type: 'array', items: { type: 'string' }, nullable: true },
    correctAnswer: { type: 'string', nullable: true }
  }
};

export const questionsListSchema: JSONSchemaType<Question[]> = {
  title: 'QuestionsList',
  type: 'array',
  items: {
    $ref: '#/definitions/question',
    type: 'object',
    required: ['id', 'question', 'correctAnswer', 'possibleAnswers']
  },
  definitions: {
    question: {
      title: 'Question',
      type: 'object',
      properties: {
        id: { type: 'integer' },
        question: { type: 'string' },
        possibleAnswers: { type: 'array', items: { type: 'string' } },
        correctAnswer: { anyOf: [{ type: 'string' }, { type: 'integer' }] }
      },
      required: ['id', 'question', 'correctAnswer', 'possibleAnswers']
    }
  }
};

О том, как писать JSON схему можно посмотреть тут https://json-schema.org/understanding-json-schema/. А сгенерировать JSON схему можно, например, тут https://www.liquid-technologies.com/online-json-to-schema-converter.

Fixtures

И последнее, что нам нужно сделать перед написанием тестов, - это описать фикстуры.

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

fixtures\users.ts

import { Fixtures } from '@playwright/test';
import { AuthUser } from '../utils/types/api/authentication';

export type UsersFixture = {
  testUser: AuthUser;
};

export const usersFixture: Fixtures<UsersFixture> = {
  testUser: async ({}, use) => {
    const email = process.env.TEST_USER_EMAIL;
    const password = process.env.TEST_USER_PASSWORD;

    if (!email || !password) {
      throw Error(`Provide "TEST_USER_EMAIL" and "TEST_USER_PASSWORD" inside .env`);
    }

    await use({ email, password });
  }
};

Теперь напишем фикстуры для questions:

fixtures\questions.ts

import { Fixtures } from '@playwright/test';
import { QuestionsAPIClient } from '../core/api/questions-api';
import { getAuthAPIContext } from '../core/context/auth-context';
import { getRandomQuestion } from '../utils/api/questions';
import { Question } from '../utils/types/api/questions';
import { UsersFixture } from './users';

export type QuestionsFixture = {
  questionsClient: QuestionsAPIClient;
  question: Question;
};

export const questionsFixture: Fixtures<QuestionsFixture, UsersFixture> = {
  questionsClient: async ({ testUser }, use) => {
    const authContext = await getAuthAPIContext({ user: testUser });
    const questionsClient = new QuestionsAPIClient(authContext);

    await use(questionsClient);
  },
  question: async ({ questionsClient }, use) => {
    const randomQuestion = getRandomQuestion();
    const question = await questionsClient.createQuestion(randomQuestion);

    await use(question);

    await questionsClient.deleteQuestionAPI(question.id);
  }
};

Фикстура questionsClient будет конструировать и передавать нам в тесты клиент для взаимодействия с questions API. Фикстура question будет создавать объект question через API и по окончанию теста удалит созданный объект.

Testing

Теперь можно писать тесты, используя все клиенты, функции, фикстуры, проверки, которые были написаны выше.

Сделаем extend стандартного test объекта из playwright и добавим в него свои фикстуры:

tests\questions-test.ts

import { test as base } from '@playwright/test';
import { questionsFixture, QuestionsFixture } from '../fixtures/questions';
import { usersFixture, UsersFixture } from '../fixtures/users';
import { combineFixtures } from '../utils/fixtures';

export const questionsTest = base.extend<UsersFixture, QuestionsFixture>(
  combineFixtures(usersFixture, questionsFixture)
);

tests\questions.spec.ts

import { getRandomQuestion, getRandomUpdateQuestion } from '../utils/api/questions';
import { assertQuestion, assertUpdateQuestion } from '../utils/assertions/api/questions';
import { expectStatusCode } from '../utils/assertions/solutions';
import { questionSchema, questionsListSchema, updateQuestionSchema } from '../utils/schema/api/questions-schema';
import { validateSchema } from '../utils/schema/validator';
import { Question } from '../utils/types/api/questions';
import { questionsTest as test } from './questions-test';

test.describe('Questions', () => {
  test('Get question', async ({ question, questionsClient }) => {
    const response = await questionsClient.getQuestionAPI(question.id);
    const json: Question = await response.json();

    await expectStatusCode({ actual: response.status(), expected: 200, api: response.url() });
    await assertQuestion({ expectedQuestion: question, actualQuestion: json });

    await validateSchema({ schema: questionSchema, json });
  });

  test('Get questions', async ({ questionsClient }) => {
    const response = await questionsClient.getQuestionsAPI();
    const json: Question[] = await response.json();

    await expectStatusCode({ actual: response.status(), expected: 200, api: response.url() });

    await validateSchema({ schema: questionsListSchema, json });
  });

  test('Create question', async ({ questionsClient }) => {
    const payload = getRandomQuestion();

    const response = await questionsClient.createQuestionAPI(payload);
    const json: Question = await response.json();

    await expectStatusCode({ actual: response.status(), expected: 201, api: response.url() });
    await assertQuestion({ expectedQuestion: payload, actualQuestion: json });

    await validateSchema({ schema: questionSchema, json });
  });

  test('Update question', async ({ question, questionsClient }) => {
    const payload = getRandomUpdateQuestion();

    const response = await questionsClient.updateQuestionAPI(question.id, payload);
    const json: Question = await response.json();

    await expectStatusCode({ actual: response.status(), expected: 200, api: response.url() });
    await assertUpdateQuestion({ expectedQuestion: payload, actualQuestion: json });

    await validateSchema({ schema: updateQuestionSchema, json });
  });

  test('Delete question', async ({ question, questionsClient }) => {
    const deleteQuestionResponse = await questionsClient.deleteQuestionAPI(question.id);
    const getQuestionResponse = await questionsClient.getQuestionAPI(question.id);

    await expectStatusCode({
      actual: getQuestionResponse.status(),
      expected: 404,
      api: getQuestionResponse.url()
    });
    await expectStatusCode({
      actual: deleteQuestionResponse.status(),
      expected: 200,
      api: deleteQuestionResponse.url()
    });
  });
});

Тут пять тестов на стандартные CRUD операции для questions API https://api.sampleapis.com/futurama/questions.

Возвращаясь к нашим требованиям:

  1. Проверяем статус код ответа, тело ответа, JSON схему;

  2. Данные готовятся внутри фикстур;

  3. На отчет посмотрим ниже.

Report

Перед генерацией отчета хочу показать одну интересную фичу playwright, с помощью которой мы можем делать глобальные setup, teardown.

В playwright.config.ts мы добавляли такую запись:

globalTeardown: require.resolve('./utils/config/global-teardown')

Которая указывает на путь к файлу, из которого экспортирована функция globalTeardown. Playwright запустит эту функцию по окончанию тестовой сессии. Для примера давайте сделаем отображение всех переменных окружения в allure отчете с помощью global-teardown:

utils\reporters\allure.ts

import fs from 'fs';
import path from 'path';

export const createAllureEnvironmentFile = (): void => {
  const reportFolder = path.resolve(process.cwd(), process.env.ALLURE_RESULTS_FOLDER);
  const environmentContent = Object.entries(process.env).reduce(
    (previousValue, [variableName, value]) => `${previousValue}\n${variableName}=${value}`,
    ''
  );

  fs.mkdirSync(reportFolder, { recursive: true });
  fs.writeFileSync(`${reportFolder}/environment.properties`, environmentContent, 'utf-8');
};

utils\config\global-teardown.ts

import { FullConfig } from '@playwright/test';
import { createAllureEnvironmentFile } from '../reporters/allure';

async function globalTeardown(_: FullConfig): Promise<void> {
  createAllureEnvironmentFile();
}

export default globalTeardown;

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

В отчете увидим переменные окружения, которые мы прописывали в globalTeardown

Запустим тесты и посмотрим на отчет:

npx playwright test

Теперь запустим отчет:

allure serve

Либо можете собрать отчет и в папке allure-reports открыть файл index.html:

allure generate

Полную версию отчета посмотрите тут.

Заключение

Весь исходный код проекта расположен на моем github.

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