Приветствую вас, читатели! Добро пожаловать в этот новый блог, где я буду демонстрировать несколько методов интеграции базы данных 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».