<style>.lazy{display:none}</style>Управление интеграцией баз данных с помощью Playwright

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

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

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

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

Настройка

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

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

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

Соединение клиента с тестовыми приспособлениями

Первый подход, который мы рассмотрим, предполагает установку клиентского соединения с помощью тестовых приспособлений. Для начала мы создаем собственный класс “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/);
  });

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

Используя этот метод, мы получаем готовые данные до запуска любого рабочего, заранее глобально настраивая их. Используя соединение с пулом, мы можем выполнять несколько запросов, сохраняя соединение активным. Однако, в силу природы плейсхолдера, когда функция глобальной настройки завершается, процесс соединения с базой данных также будет завершен, даже при использовании функции 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”, но основная идея остается неизменной для большинства других типов баз данных. К этому моменту мы должны были понять, как использовать глобальную настройку, управлять тестовыми приспособлениями, работать с зависимостями проекта и легко интегрировать тесты с базой данных или любыми другими внешними сервисами.

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

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

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