Что пишут в блогах

Подписаться

Что пишут в блогах (EN)

Разделы портала

Онлайн-тренинги

.
Руководство по аутентификации в Playwright
05.11.2025 00:00

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

Аутентификация – как правило, первое препятствие при настройке автоматизации тестирования. В зависимости от сложности используемого метода аутентификации эта задача может оказаться весьма трудоёмкой. Давайте начнём с простого примера последовательности входа в систему.

login.spec.ts

1  import { test, expect } from '@playwright/test';
2
3  test("login", async ({ page }) => {
4    await page.goto('/login');
5    await page.getByLabel('Email').fill(' Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ');
6    await page.getByLabel('Password').fill('password');
7    await page.getByRole('button', { name: 'Login' }).click();
8    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
9  });

Абстрагирование шагов входа

Поскольку эту последовательность, вероятно, придётся повторять во множестве тестов, её можно вынести в отдельный блок. Это может быть функция, модуль или Page Object.

Функциональный модуль

В случае функции модуль может выглядеть так:

login.ts

1  export const login = async (page: Page) => {
2    await page.goto('/login');
3    await page.getByLabel('Email').fill(' Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ');
4    await page.getByLabel('Password').fill('password');
5    await page.getByRole('button', { name: 'Login' }).click();
6    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
7  }

Теперь мы можем использовать эту функцию в нашем тесте.

login.spec.ts

1  import { test, expect } from '@playwright/test';
2  import { login } from './login';
3
4  test("login", async ({ page }) => {
5    await login(page);
6    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
7  });

Page Object

Поскольку Playwright поощряет использование Page Object, можно создать примерно такой объект:

login.ts

1  export class LoginPage {
2    constructor(private page: Page) {}
3
4    async login() {
5      await this.page.goto('/login');
6      await this.page.getByLabel('Email').fill(' Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ');
7      await this.page.getByLabel('Password').fill('password');
8      await this.page.getByRole('button', { name: 'Login' }).click();
9      await expect(this.page.getByText('Welcome, Filip!')).toBeVisible();
10    }
11  }

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

login.spec.ts

1  import { test, expect } from '@playwright/test';
2  import { LoginPage } from './login';
3
4  test("login", async ({ page }) => {
5    const loginPage = new LoginPage(page);
6    await loginPage.login();
7    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
8  });

Хранение состояния браузера

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

С современными инструментами для веб-тестирования, такими как Playwright, этого можно избежать. Мы можем сохранить состояние аутентификации и использовать его в нескольких тестах. Для этого воспользуемся двумя возможностями Playwright: context.storageState и test.use.

context.storageState — это свойство, которое позволяет сохранить состояние браузера. Его можно либо записать в отдельный файл, либо вернуть из функции и сохранить в переменной. Вот простой пример, демонстрирующий, как это работает:

login.spec.ts

1  import { test, expect } from '@playwright/test';
2
3  test("login", async ({ page }) => {
4    await page.goto('/login');
5    await page.getByLabel('Email').fill(' Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ');
6    await page.getByLabel('Password').fill('password');
7    await page.getByRole('button', { name: 'Login' }).click();
8    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
9    await page.context().storageState({ path: 'playwright/.auth.json' });
10  });

Это сохранит куки браузера и состояние локального хранилища в файл playwright/.auth.json.

Этот файл затем можно переиспользовать в нескольких тестах. Функция test.use — это хук, который позволяет применять сохранённое состояние аутентификации в тестах.

login.spec.ts

1  import { test, expect } from '@playwright/test';
2
3  test.use({ storageState: 'playwright/.auth.json' });
4
5  test("login", async ({ page }) => {
6    await page.goto('/login');
7    await expect(page.getByText('Welcome, Filip!')).toBeVisible();
8  });

Внимание!

Если вы сохраняете состояние браузера в файл, убедитесь, что этот файл или папка не попадают в ваш репозиторий. Этот файл содержит конфиденциальную информацию. Используйте .gitignore, чтобы исключить файл или папку из коммитов:

.gitignore

1  playwright/.auth.json

Глобальная настройка

Поскольку большинство тестов требуют аутентификации, мы можем создать файл настройки, который будет служить зависимостью для всех наших тестов. Таким образом, настройка будет подключаться глобально, и не нужно будет повторять её в каждом тесте. Хорошей практикой будет использование расширения .setup.ts вместо .spec.ts для таких файлов, чтобы отделять их от самих тестов. Файлы настройки станут удобным местом для всей логики, отвечающей за подготовку состояния, данных или других зависимостей для тестов.

auth.setup.ts

1  import { test as setup, expect } from '@playwright/test';
2
3  setup("user authentication", async ({ page }) => {
4    await page.goto('/');
5    await expect(page.getByTestId('cookie-consent-message')).toBeVisible();
6
7    await page.getByRole('button', { name: 'Accept' }).click();
8    await expect(page.getByText('Cookie Preference Saved')).toBeVisible();
9
10    await page.context().storageState({ path: 'playwright/.auth.json' });
11  });

Как только файл настройки создан, мы можем указать его в качестве зависимости в playwright.config.ts. Логика работы тут следующая:

  1. Проект setup используется для запуска файла настройки. Он выполнит наш файл auth.setup.ts, который создаёт файл playwright/.auth.json с состоянием браузера (строки 6-9).
  2. Проект chromium зависит от проекта setup, поэтому перед запуском тестов он сначала выполнит файл настройки (строка 16).
  3. Проект chromium используется для запуска тестов и применяет файл playwright/.auth.json в качестве состояния браузера (строка 14).

playwright.config.ts

1  import { defineConfig } from '@playwright/test';
2
3  export default defineConfig({
4    testDir: './tests',
5    projects: [
6      {
7        name: 'setup',
8        testMatch: /.*\.setup\.ts/
9      },
10      {
11        name: 'chromium',
12        use: {
13          ...devices['Desktop Chrome'],
14          storageState: './playwright/.auth.json'
15        },
16        dependencies: ['setup'],
17      },
18    ],
19  });

Обратите внимание, что если вы хотите запускать тесты в режиме --ui, нужно убедиться, что проект setup включён в список проектов, иначе он будет пропущен. Это может быть очевидно для опытных пользователей, но мне при первой настройке это не сразу пришло в голову (в headless-режиме всё работает нормально, так как там по умолчанию запускаются все проекты).


Подход с файлом настройки работает отлично, так как вы одновременно тестируете и процесс логина. Как только вход успешен, Playwright переходит к другим тестам. Однако бывают случаи, когда вам не нужно быть залогиненным. Для таких тестов можно просто использовать хук test.use и сбросить состояние для текущего теста.

redirect.spec.ts

1  import { test, expect } from '@playwright/test';
2
// Reset the state for current test
4  test.use({ storageState: { cookies: [], origins: [] } });
5
6  test("redirected to login", async ({ page }) => {
7    await page.goto('/home');
8    await expect(page.getByText('Please login to continue')).toBeVisible();
9  });

Сохранение состояния и повторное его использование в нескольких тестах — лучший способ работы с аутентификацией. Это сокращает время выполнения тестов и делает их более надёжными. Если ваше приложение защищено от атак перебором (как оно и должно быть), вы ограничите количество запросов к эндпойнту логина. Это снижает нагрузку на бэкенд и уменьшает вероятность блокировки из-за капчи или других мер безопасности.

Обсудить в форуме