Управление интеграцией баз данных с помощью Playwright

Перевод статьи «Managing Database Integration With Playwright».

Приветствую вас, читатели! В этой статье я продемонстрирую несколько методов интеграции базы данных PostgreSQL с Playwright. Обычно не рекомендуется интегрировать базы данных непосредственно в сквозные тесты из-за соображений безопасности баз данных, но есть случаи, когда это становится необходимым.

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

Содержание

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

Настройка

Все, что вам нужно сделать – это установить библиотеку Postgres. Поскольку я буду показывать, как это сделать с помощью TypeScript, вам также потребуется установить библиотеку types/pg.

Код, прописывание зависимостей.

Инициализация БД

Существует два основных способа установить соединение с базой данных. Первый способ – это клиентское соединение, которое создается только для выполнения определенного SQL-запроса, а затем закрывается. Второй способ – использование пула соединений. При использовании пула соединений вы устанавливаете соединение с базой данных и поддерживаете его активным до тех пор, пока не закончите работу с ним или не завершите процесс.

Клиентское соединение с тестовыми фикстурами

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

Класс "DB"

Для улучшения обработки ошибок мы можем реализовать подход, как показано ниже:

  async executeQuery(query: string) {
    const client = new Client(this.DBConfig);
    try {
      await client.connect();
      const result = await client.query(query);
      console.log(result.rows);
    } catch (error) {
      console.error("Error in connection/executing query:", error);
    } finally {
      await client.end().catch((error) => {
        console.error("Error ending client connection:", error);
      });
    }
  };

Пришло время создать соответствующую тестовую фикстуру:

Тестовая фикстура

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

test("[Desktop] Should be able to login", async ({ page, dataBase, loginPage }) => {
    const seedPassword = "Test123";
    const password = await hash(seedPassword, 12);
    await dataBase.executeQuery(`INSERT INTO public."User" (username, password) VALUES ('testUser', '${password}');`);
    await loginPage.loginWith("testUser", "Test123");
    await expect(page).toHaveURL(/.*feedback/);
  });
Выполнение теста по добавлению пользователя

В рассмотренном нами тесте мы поместили имя пользователя и пароль в базу данных, сделав пароль хэшированным с помощью библиотеки bcrypt. Затем мы использовали наш метод login, чтобы позволить новому пользователю войти в систему с новыми учетными данными. Мы убедились, что вход в систему прошел, так как проверили ожидаемый url.

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

 test.beforeEach(async ({ dataBase, page }) => {
    const seedPassword = "Test123";
    const password = await hash(seedPassword, 12);
    await dataBase.executeQuery(`INSERT INTO public."User" (username, password) VALUES ('testUser', '${password}');`);
    await page.goto("/");
  });

  test.afterEach(async ({ dataBase }) => {
    await dataBase.executeQuery(`DELETE FROM public."User" WHERE username = 'testUser';`);
  });

  test("[Desktop] Should be able to login", async ({ page, loginPage }) => {
    await loginPage.loginWith("testUser", "Test123");
    await expect(page).toHaveURL(/.*feedback/);
  });

Пул соединений в глобальной настройке

Используя этот метод, мы получаем готовые данные до запуска любого воркера, заранее глобально их настраивая. Используя пул соединений, мы можем выполнять несколько запросов, сохраняя соединение активным. Однако, в силу природы Playwright, когда функция глобальной настройки завершается, процесс соединения с базой данных также будет завершен, даже при использовании функции-одиночки (singleton).

Мы можем повторно использовать тот же класс DB, который создали в предыдущем примере, с небольшими изменениями:

import { Pool, PoolClient, QueryResult } from "pg";

export default class DB {
  private pool: Pool;

  private DBConfig = {
    user: "db_user",
    host: "localhost",
    database: "local_db",
    password: "test",
    port: 5477,
    max: 10,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
    allowExitOnIdle: false,
  };

  async getDBConnection(): Promise<PoolClient> {
    if (!this.pool) {
      this.pool = new Pool(this.DBConfig);
      const client = await this.pool.connect();
      console.log(`---------> √ DB connection has been established! <---------`);
      return client;
    } else {
      return this.pool.connect();
    }
  }

  async executeQuery(query: string): Promise<void> {
    try {
      const client: PoolClient = await this.getDBConnection();
      const result: QueryResult = await client.query(query);
      console.log(result.rows);
    } catch (error) {
      console.error("Error executing query:", error);
    }
  }
}

Затем вызовем нашу функцию в файле globalSetup.ts:

import DB from "./db";
import { hash } from "bcrypt";

const connectToDB = new DB();
const seedPassword = "Test123";
const password = await hash(seedPassword, 12);

async function establishDBConnection() {
  try {
    await connectToDB.getDBConnection();
  } catch (error) {
    console.log(`---------> X Failed to connect to dataBase <--------- \n\n ${error}`);
    process.exit(1);
  }
}

async function globalSetup() {
  await establishDBConnection();
  await connectToDB.executeQuery(`DELETE FROM public."User" WHERE username = 'testUser';`);
  await connectToDB.executeQuery('SELECT * FROM public."User"');
  await connectToDB.executeQuery(`INSERT INTO public."User" (username, password) VALUES ('testUser', '${password}');`);
}

export default globalSetup;

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

Тест входа в систему

Запуск теста, результаты в терминале

Пул соединений с зависимостями от проекта

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

projects: [
        {
          name: "prerequisites",
          testMatch: "**/*/dataBaseSetup.ts",
        },
        {
          name: "Chrome",
          use: {
            browserName: "chromium",
          },
          dependencies: ["prerequisites"]
        }
      ];

Далее мы включаем проекты, где мы задали проект с именем “prerequisites”. Используйте настройку testMatch, чтобы указать на конфигурационный файл теста databaseSetup.ts.

import { test as setup } from "@playwright/test";
import DB from "../utils/db";
import { hash } from "bcrypt";

setup("Prepare the pre-requisites", async () => {
  const db = new DB();  // test fixture can be used as well
  await db.getDBConnection();
  const seedPassword = "Test123";
  const password = await hash(seedPassword, 12);
  await db.executeQuery(`DELETE FROM public."User" WHERE username = 'testUser';`);
  await db.executeQuery(`INSERT INTO public."User" (username, password) VALUES ('testUser', '${password}');`);
  await db.executeQuery('SELECT * FROM public."User"');
});
Установление связи с базой данных

Заключение

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

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *