Skip to main content

Аутентификация

Введение

Playwright выполняет тесты в изолированных средах, называемых контекстами браузера. Эта модель изоляции улучшает воспроизводимость и предотвращает каскадные сбои тестов. Тесты могут загружать существующее состояние аутентификации. Это устраняет необходимость аутентификации в каждом тесте и ускоряет выполнение тестов.

Основные концепции

Независимо от выбранной стратегии аутентификации, вы, вероятно, будете хранить аутентифицированное состояние браузера в файловой системе.

Мы рекомендуем создать директорию playwright/.auth и добавить её в ваш .gitignore. Ваша процедура аутентификации создаст аутентифицированное состояние браузера и сохранит его в файл в этой директории playwright/.auth. В дальнейшем тесты будут повторно использовать это состояние и начинать уже аутентифицированными.

mkdir -p playwright/.auth
echo $'\nplaywright/.auth' >> .gitignore

Базовый: общий аккаунт во всех тестах

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

Когда использовать

  • Когда вы можете представить, что все ваши тесты выполняются одновременно с одной и той же учетной записью, не влияя друг на друга.

Когда не использовать

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

Детали

Создайте tests/auth.setup.ts, который подготовит аутентифицированное состояние браузера для всех других тестов.

tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, '../playwright/.auth/user.json');

setup('authenticate', async ({ page }) => {
// Выполните шаги аутентификации. Замените эти действия на свои собственные.
await page.goto('https://github.com/login');
await page.getByLabel('Username or email address').fill('username');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Подождите, пока страница не получит куки.
//
// Иногда в процессе нескольких перенаправлений устанавливаются куки.
// Подождите окончательного URL, чтобы убедиться, что куки действительно установлены.
await page.waitForURL('https://github.com/');
// В качестве альтернативы вы можете подождать, пока страница не достигнет состояния, в котором все куки установлены.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();

// Конец шагов аутентификации.

await page.context().storageState({ path: authFile });
});

Создайте новый проект setup в конфигурации и объявите его как зависимость для всех ваших тестовых проектов. Этот проект всегда будет запускаться и аутентифицироваться перед всеми тестами. Все тестовые проекты должны использовать аутентифицированное состояние как storageState.

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
projects: [
// Проект настройки
{ name: 'setup', testMatch: /.*\.setup\.ts/ },

{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Используйте подготовленное состояние аутентификации.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
// Используйте подготовленное состояние аутентификации.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});

Тесты начинаются уже аутентифицированными, потому что мы указали storageState в конфигурации.

tests/example.spec.ts
import { test } from '@playwright/test';

test('test', async ({ page }) => {
// страница аутентифицирована
});

Обратите внимание, что вам нужно удалить сохраненное состояние, когда оно истечет. Если вам не нужно сохранять состояние между запусками тестов, запишите состояние браузера в testProject.outputDir, который автоматически очищается перед каждым запуском теста.

Аутентификация в режиме UI

Режим UI по умолчанию не будет запускать проект setup, чтобы улучшить скорость тестирования. Мы рекомендуем аутентифицироваться, вручную запуская auth.setup.ts время от времени, когда существующая аутентификация истекает.

Сначала включите проект setup в фильтрах, затем нажмите кнопку треугольника рядом с файлом auth.setup.ts, а затем снова отключите проект setup в фильтрах.

Умеренный: одна учетная запись на параллельный рабочий процесс

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

Когда использовать

  • Ваши тесты изменяют общее серверное состояние. Например, один тест проверяет отображение страницы настроек, в то время как другой тест изменяет настройку.

Когда не использовать

  • Ваши тесты не изменяют общее серверное состояние. В этом случае все тесты могут использовать одну общую учетную запись.

Детали

Мы будем аутентифицироваться один раз на рабочий процесс, каждый с уникальной учетной записью.

Создайте файл playwright/fixtures.ts, который будет переопределять фикстуру storageState, чтобы аутентифицироваться один раз на рабочий процесс. Используйте testInfo.parallelIndex, чтобы различать рабочие процессы.

playwright/fixtures.ts
import { test as baseTest, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';

export * from '@playwright/test';
export const test = baseTest.extend<{}, { workerStorageState: string }>({
// Используйте одно и то же состояние хранения для всех тестов в этом рабочем процессе.
storageState: ({ workerStorageState }, use) => use(workerStorageState),

// Аутентифицируйтесь один раз на рабочий процесс с фикстурой, привязанной к рабочему процессу.
workerStorageState: [async ({ browser }, use) => {
// Используйте parallelIndex как уникальный идентификатор для каждого рабочего процесса.
const id = test.info().parallelIndex;
const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`);

if (fs.existsSync(fileName)) {
// Повторно используйте существующее состояние аутентификации, если оно есть.
await use(fileName);
return;
}

// Важно: убедитесь, что мы аутентифицируемся в чистой среде, сбросив состояние хранения.
const page = await browser.newPage({ storageState: undefined });

// Получите уникальную учетную запись, например, создайте новую.
// В качестве альтернативы, у вас может быть список предварительно созданных учетных записей для тестирования.
// Убедитесь, что учетные записи уникальны, чтобы несколько членов команды
// могли запускать тесты одновременно без помех.
const account = await acquireAccount(id);

// Выполните шаги аутентификации. Замените эти действия на свои собственные.
await page.goto('https://github.com/login');
await page.getByLabel('Username or email address').fill(account.username);
await page.getByLabel('Password').fill(account.password);
await page.getByRole('button', { name: 'Sign in' }).click();
// Подождите, пока страница не получит куки.
//
// Иногда в процессе нескольких перенаправлений устанавливаются куки.
// Подождите окончательного URL, чтобы убедиться, что куки действительно установлены.
await page.waitForURL('https://github.com/');
// В качестве альтернативы вы можете подождать, пока страница не достигнет состояния, в котором все куки установлены.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();

// Конец шагов аутентификации.

await page.context().storageState({ path: fileName });
await page.close();
await use(fileName);
}, { scope: 'worker' }],
});

Теперь каждый тестовый файл должен импортировать test из нашего файла с фикстурами вместо @playwright/test. Изменения в конфигурации не требуются.

tests/example.spec.ts
// Важно: импортируйте наши фикстуры.
import { test, expect } from '../playwright/fixtures';

test('test', async ({ page }) => {
// страница аутентифицирована
});

Продвинутые сценарии

Аутентификация с помощью API-запроса

Когда использовать

  • Ваше веб-приложение поддерживает аутентификацию через API, что проще/быстрее, чем взаимодействие с интерфейсом приложения.

Детали

Мы отправим API-запрос с помощью APIRequestContext, а затем сохраним аутентифицированное состояние как обычно.

В проекте настройки:

tests/auth.setup.ts
import { test as setup } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ request }) => {
// Отправьте запрос на аутентификацию. Замените на свой собственный.
await request.post('https://github.com/login', {
form: {
'user': 'user',
'password': 'password'
}
});
await request.storageState({ path: authFile });
});

В качестве альтернативы, в фикстуре рабочего процесса:

playwright/fixtures.ts
import { test as baseTest, request } from '@playwright/test';
import fs from 'fs';
import path from 'path';

export * from '@playwright/test';
export const test = baseTest.extend<{}, { workerStorageState: string }>({
// Используйте одно и то же состояние хранения для всех тестов в этом рабочем процессе.
storageState: ({ workerStorageState }, use) => use(workerStorageState),

// Аутентифицируйтесь один раз на рабочий процесс с фикстурой, привязанной к рабочему процессу.
workerStorageState: [async ({}, use) => {
// Используйте parallelIndex как уникальный идентификатор для каждого рабочего процесса.
const id = test.info().parallelIndex;
const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`);

if (fs.existsSync(fileName)) {
// Повторно используйте существующее состояние аутентификации, если оно есть.
await use(fileName);
return;
}

// Важно: убедитесь, что мы аутентифицируемся в чистой среде, сбросив состояние хранения.
const context = await request.newContext({ storageState: undefined });

// Получите уникальную учетную запись, например, создайте новую.
// В качестве альтернативы, у вас может быть список предварительно созданных учетных записей для тестирования.
// Убедитесь, что учетные записи уникальны, чтобы несколько членов команды
// могли запускать тесты одновременно без помех.
const account = await acquireAccount(id);

// Отправьте запрос на аутентификацию. Замените на свой собственный.
await context.post('https://github.com/login', {
form: {
'user': 'user',
'password': 'password'
}
});

await context.storageState({ path: fileName });
await context.dispose();
await use(fileName);
}, { scope: 'worker' }],
});

Несколько вошедших ролей

Когда использовать

  • У вас есть более одной роли в ваших end-to-end тестах, но вы можете повторно использовать учетные записи во всех тестах.

Детали

Мы будем аутентифицироваться несколько раз в проекте настройки.

tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';

const adminFile = 'playwright/.auth/admin.json';

setup('authenticate as admin', async ({ page }) => {
// Выполните шаги аутентификации. Замените эти действия на свои собственные.
await page.goto('https://github.com/login');
await page.getByLabel('Username or email address').fill('admin');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Подождите, пока страница не получит куки.
//
// Иногда в процессе нескольких перенаправлений устанавливаются куки.
// Подождите окончательного URL, чтобы убедиться, что куки действительно установлены.
await page.waitForURL('https://github.com/');
// В качестве альтернативы вы можете подождать, пока страница не достигнет состояния, в котором все куки установлены.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();

// Конец шагов аутентификации.

await page.context().storageState({ path: adminFile });
});

const userFile = 'playwright/.auth/user.json';

setup('authenticate as user', async ({ page }) => {
// Выполните шаги аутентификации. Замените эти действия на свои собственные.
await page.goto('https://github.com/login');
await page.getByLabel('Username or email address').fill('user');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Подождите, пока страница не получит куки.
//
// Иногда в процессе нескольких перенаправлений устанавливаются куки.
// Подождите окончательного URL, чтобы убедиться, что куки действительно установлены.
await page.waitForURL('https://github.com/');
// В качестве альтернативы вы можете подождать, пока страница не достигнет состояния, в котором все куки установлены.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();

// Конец шагов аутентификации.

await page.context().storageState({ path: userFile });
});

После этого укажите storageState для каждого тестового файла или группы тестов, вместо установки его в конфигурации.

tests/example.spec.ts
import { test } from '@playwright/test';

test.use({ storageState: 'playwright/.auth/admin.json' });

test('admin test', async ({ page }) => {
// страница аутентифицирована как администратор
});

test.describe(() => {
test.use({ storageState: 'playwright/.auth/user.json' });

test('user test', async ({ page }) => {
// страница аутентифицирована как пользователь
});
});

См. также о аутентификации в режиме UI.

Тестирование нескольких ролей вместе

Когда использовать

  • Вам нужно протестировать, как несколько аутентифицированных ролей взаимодействуют вместе в одном тесте.

Детали

Используйте несколько BrowserContext и Page с разными состояниями хранения в одном тесте.

tests/example.spec.ts
import { test } from '@playwright/test';

test('admin and user', async ({ browser }) => {
// adminContext и все страницы внутри, включая adminPage, вошли как "admin".
const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const adminPage = await adminContext.newPage();

// userContext и все страницы внутри, включая userPage, вошли как "user".
const userContext = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
const userPage = await userContext.newPage();

// ... взаимодействуйте с adminPage и userPage ...

await adminContext.close();
await userContext.close();
});

Тестирование нескольких ролей с фикстурами POM

Когда использовать

  • Вам нужно протестировать, как несколько аутентифицированных ролей взаимодействуют вместе в одном тесте.

Детали

Вы можете ввести фикстуры, которые будут предоставлять страницу, аутентифицированную как каждая роль.

Ниже приведен пример, который создает фикстуры для двух Page Object Models - администраторской POM и пользовательской POM. Предполагается, что файлы adminStorageState.json и userStorageState.json были созданы в глобальной настройке.

playwright/fixtures.ts
import { test as base, type Page, type Locator } from '@playwright/test';

// Page Object Model для страницы "admin".
// Здесь вы можете добавить локаторы и вспомогательные методы, специфичные для страницы администратора.
class AdminPage {
// Страница, вошедшая как "admin".
page: Page;

// Пример локатора, указывающего на приветствие "Welcome, Admin".
greeting: Locator;

constructor(page: Page) {
this.page = page;
this.greeting = page.locator('#greeting');
}
}

// Page Object Model для страницы "user".
// Здесь вы можете добавить локаторы и вспомогательные методы, специфичные для страницы пользователя.
class UserPage {
// Страница, вошедшая как "user".
page: Page;

// Пример локатора, указывающего на приветствие "Welcome, User".
greeting: Locator;

constructor(page: Page) {
this.page = page;
this.greeting = page.locator('#greeting');
}
}

// Объявите типы ваших фикстур.
type MyFixtures = {
adminPage: AdminPage;
userPage: UserPage;
};

export * from '@playwright/test';
export const test = base.extend<MyFixtures>({
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const adminPage = new AdminPage(await context.newPage());
await use(adminPage);
await context.close();
},
userPage: async ({ browser }, use) => {
const context = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
const userPage = new UserPage(await context.newPage());
await use(userPage);
await context.close();
},
});

tests/example.spec.ts
// Импортируйте тест с нашими новыми фикстурами.
import { test, expect } from '../playwright/fixtures';

// Используйте фикстуры adminPage и userPage в тесте.
test('admin and user', async ({ adminPage, userPage }) => {
// ... взаимодействуйте с adminPage и userPage ...
await expect(adminPage.greeting).toHaveText('Welcome, Admin');
await expect(userPage.greeting).toHaveText('Welcome, User');
});

Хранение сессии

Повторное использование аутентифицированного состояния охватывает аутентификацию на основе куки, локального хранилища и IndexedDB. Редко хранилище сессии используется для хранения информации, связанной с состоянием входа. Хранилище сессии специфично для определенного домена и не сохраняется между загрузками страниц. Playwright не предоставляет API для сохранения хранилища сессии, но следующий фрагмент кода может быть использован для сохранения/загрузки хранилища сессии.

// Получите хранилище сессии и сохраните как переменную окружения
const sessionStorage = await page.evaluate(() => JSON.stringify(sessionStorage));
fs.writeFileSync('playwright/.auth/session.json', sessionStorage, 'utf-8');

// Установите хранилище сессии в новом контексте
const sessionStorage = JSON.parse(fs.readFileSync('playwright/.auth/session.json', 'utf-8'));
await context.addInitScript(storage => {
if (window.location.hostname === 'example.com') {
for (const [key, value] of Object.entries(storage))
window.sessionStorage.setItem(key, value);
}
}, sessionStorage);

Избегание аутентификации в некоторых тестах

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

not-signed-in.spec.ts
import { test } from '@playwright/test';

// Сбросьте состояние хранения для этого файла, чтобы избежать аутентификации
test.use({ storageState: { cookies: [], origins: [] } });

test('not signed in test', async ({ page }) => {
// ...
});