Перевод статьи «Managing Database Integration With 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/); });
Пул соединений в глобальной настройке
Используя этот метод, мы получаем готовые данные до запуска любого воркера, заранее глобально их настраивая. Используя пул соединений, мы можем выполнять несколько запросов, сохраняя соединение активным. Однако, в силу природы 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. Вы узнали, как использовать глобальную настройку, управлять тестовыми фикстурами, обрабатывать зависимости проекта и легко интегрировать тесты с базой данных или любыми другими внешними сервисами.