Перейти к основному содержимому

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

Введение​

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

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

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

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

осторожно

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

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 }) => {
// ...
});