Перевод статьи «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. Вы узнали, как использовать глобальную настройку, управлять тестовыми фикстурами, обрабатывать зависимости проекта и легко интегрировать тесты с базой данных или любыми другими внешними сервисами.
