Авторизация
Введение
Playwright выполняет тесты в изолированных средах, называемых контекстами браузера. Эта модель изоляции улучшает воспроизводимость и предотвращает каскадные сбои тестов. Тесты могут загружать существующее состояние авторизации. Это устраняет необходимость авторизации в каждом тесте и ускоряет выполнение тестов.
Основные понятия
Независимо от выбранной стратегии авторизации, вы, скорее всего, будете хранить аутентифицированное состояние браузера на файловой системе.
Мы рекомендуем создать директорию playwright/.auth и добавить её в .gitignore. Ваша процедура авторизации будет генерировать аутентифицированное состояние браузера и сохранять его в файл в этой директории playwright/.auth. Позже тесты будут повторно использовать это состояние и начнут работу уже с авторизованным браузером.
- Bash
- PowerShell
- Batch
mkdir -p playwright/.auth echo $'\nplaywright/.auth' >> .gitignore
New-Item -ItemType Directory -Force -Path playwright\.auth Add-Content -path .gitignore "`r`nplaywright/.auth"
md playwright\.auth echo. >> .gitignore echo "playwright/.auth" >> .gitignore
Базовый: общий учётная запись во всех тестах
Это рекомендуемый подход для тестов без состояния на стороне сервера. Авторизуйтесь один раз в проекте настройки, сохраните состояние авторизации и затем используйте его для запуска каждого теста с уже авторизованным состоянием.
Когда использовать
- Когда вы можете представить, что все ваши тесты выполняются одновременно с одной и той же учётной записью без взаимного влияния.
Когда не использовать
- Если ваши тесты изменяют состояние на стороне сервера. Например, один тест проверяет отображение страницы настроек, а другой тест изменяет настройку, и вы запускаете тесты параллельно. В этом случае тесты должны использовать разные учётные записи.
- Если ваша авторизация зависит от браузера.
Подробности
Создайте 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 }) => {
// Perform authentication steps. Replace these actions with your own.
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();
// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await page.waitForURL('https://github.com/');
// Alternatively, you can wait until the page reaches a state where all cookies are set.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
// End of authentication steps.
await page.context().storageState({ path: authFile });
}); Создайте новый setup проект в конфигурации и объявите его зависимостью зависимостью для всех ваших тестовых проектов. Этот проект всегда будет выполняться и авторизовываться перед всеми тестами. Все тестовые проекты должны использовать аутентифицированное состояние как storageState.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
// Setup project
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
}); Тесты запускаются уже авторизованными, потому что мы указали storageState в конфигурации.
import { test } from '@playwright/test';
test('test', async ({ page }) => {
// page is authenticated
}); Обратите внимание, что вам нужно удалить сохранённое состояние, когда оно истечёт. Если вам не нужно сохранять состояние между запусками тестов, сохраните состояние браузера в testProject.outputDir, которое автоматически очищается перед каждым запуском теста.
Авторизация в режиме UI
Режим UI по умолчанию не будет запускать setup проект, чтобы ускорить тестирование. Мы рекомендуем периодически вручную запускать auth.setup.ts, когда истечёт существующая авторизация.
Сначала включите setup проект в фильтрах, затем щелкните треугольную кнопку рядом с файлом auth.setup.ts, а затем снова отключите setup проект в фильтрах.
Умеренный: одна учётная запись на параллельный процесс
Это рекомендуемый подход для тестов, которые изменяют состояние на стороне сервера. В Playwright рабочие процессы выполняются параллельно. В этом подходе каждый параллельный рабочий процесс авторизуется один раз. Все тесты, запущенные рабочим процессом, повторно используют одно и то же состояние авторизации. Нам понадобятся несколько тестовых учётных записей, по одной на каждый параллельный рабочий процесс.
Когда использовать
- Если ваши тесты изменяют общее состояние на стороне сервера. Например, один тест проверяет отображение страницы настроек, а другой изменяет настройку.
Когда не использовать
- Если ваши тесты не изменяют общее состояние на стороне сервера. В этом случае все тесты могут использовать одну общую учётную запись.
Подробности
Мы будем авторизовываться один раз на каждый рабочий процесс с уникальной учётной записью.
Создайте playwright/fixtures.ts файл, который переопределит storageState фикстуру для авторизации один раз на каждый рабочий процесс. Используйте testInfo.parallelIndex для различения рабочих процессов.
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 }>({
// Use the same storage state for all tests in this worker.
storageState: ({ workerStorageState }, use) => use(workerStorageState),
// Authenticate once per worker with a worker-scoped fixture.
workerStorageState: [async ({ browser }, use) => {
// Use parallelIndex as a unique identifier for each worker.
const id = test.info().parallelIndex;
const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`);
if (fs.existsSync(fileName)) {
// Reuse existing authentication state if any.
await use(fileName);
return;
}
// Important: make sure we authenticate in a clean environment by unsetting storage state.
const page = await browser.newPage({ storageState: undefined });
// Acquire a unique account, for example create a new one.
// Alternatively, you can have a list of precreated accounts for testing.
// Make sure that accounts are unique, so that multiple team members
// can run tests at the same time without interference.
const account = await acquireAccount(id);
// Perform authentication steps. Replace these actions with your own.
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();
// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await page.waitForURL('https://github.com/');
// Alternatively, you can wait until the page reaches a state where all cookies are set.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
// End of authentication steps.
await page.context().storageState({ path: fileName });
await page.close();
await use(fileName);
}, { scope: 'worker' }],
}); Теперь каждый файл теста должен импортировать test из нашего файла фикстур вместо @playwright/test. Изменения в конфигурации не требуются.
// Important: import our fixtures.
import { test, expect } from '../playwright/fixtures';
test('test', async ({ page }) => {
// page is authenticated
}); Расширенные сценарии
Авторизация с помощью запроса API
Когда использовать
- Если ваше веб-приложение поддерживает авторизацию через API, что проще/быстрее, чем взаимодействие с пользовательским интерфейсом.
Подробности
Мы отправим запрос API с помощью APIRequestContext, а затем сохраним состояние авторизации как обычно.
import { test as setup } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ request }) => {
// Send authentication request. Replace with your own.
await request.post('https://github.com/login', {
form: {
'user': 'user',
'password': 'password'
}
});
await request.storageState({ path: authFile });
}); Или в фикстуре рабочего процесса:
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 }>({
// Use the same storage state for all tests in this worker.
storageState: ({ workerStorageState }, use) => use(workerStorageState),
// Authenticate once per worker with a worker-scoped fixture.
workerStorageState: [async ({}, use) => {
// Use parallelIndex as a unique identifier for each worker.
const id = test.info().parallelIndex;
const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`);
if (fs.existsSync(fileName)) {
// Reuse existing authentication state if any.
await use(fileName);
return;
}
// Important: make sure we authenticate in a clean environment by unsetting storage state.
const context = await request.newContext({ storageState: undefined });
// Acquire a unique account, for example create a new one.
// Alternatively, you can have a list of precreated accounts for testing.
// Make sure that accounts are unique, so that multiple team members
// can run tests at the same time without interference.
const account = await acquireAccount(id);
// Send authentication request. Replace with your own.
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 тестах, но вы можете повторно использовать учётные записи во всех тестах.
Подробности
Мы выполним несколько авторизаций в проекте настройки.
import { test as setup, expect } from '@playwright/test';
const adminFile = 'playwright/.auth/admin.json';
setup('authenticate as admin', async ({ page }) => {
// Perform authentication steps. Replace these actions with your own.
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();
// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await page.waitForURL('https://github.com/');
// Alternatively, you can wait until the page reaches a state where all cookies are set.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
// End of authentication steps.
await page.context().storageState({ path: adminFile });
});
const userFile = 'playwright/.auth/user.json';
setup('authenticate as user', async ({ page }) => {
// Perform authentication steps. Replace these actions with your own.
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();
// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await page.waitForURL('https://github.com/');
// Alternatively, you can wait until the page reaches a state where all cookies are set.
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
// End of authentication steps.
await page.context().storageState({ path: userFile });
}); После этого укажите storageState для каждого файла или группы тестов, вместо установки в конфигурации.
import { test } from '@playwright/test';
test.use({ storageState: 'playwright/.auth/admin.json' });
test('admin test', async ({ page }) => {
// page is authenticated as admin
});
test.describe(() => {
test.use({ storageState: 'playwright/.auth/user.json' });
test('user test', async ({ page }) => {
// page is authenticated as a user
});
}); См. также авторизацию в режиме UI.
Тестирование нескольких ролей вместе
Когда использовать
- Если вам нужно проверить, как взаимодействуют несколько авторизованных ролей в одном тесте.
Подробности
Используйте несколько BrowserContext и Page с различными состояниями хранилища в одном тесте.
import { test } from '@playwright/test';
test('admin and user', async ({ browser }) => {
// adminContext and all pages inside, including adminPage, are signed in as "admin".
const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const adminPage = await adminContext.newPage();
// userContext and all pages inside, including userPage, are signed in as "user".
const userContext = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
const userPage = await userContext.newPage();
// ... interact with both adminPage and userPage ...
await adminContext.close();
await userContext.close();
}); Тестирование нескольких ролей с фикстурами POM
Когда использовать
- Если вам нужно проверить, как взаимодействуют несколько авторизованных ролей в одном тесте.
Подробности
Вы можете добавить фикстуры, которые предоставят страницу, авторизованную как каждая роль.
Ниже приведен пример, который создает фикстуры для двух моделей страниц объекта - модели POM администратора и модели POM пользователя. Предполагается, что файлы adminStorageState.json и userStorageState.json были созданы в глобальной настройке.
import { test as base, type Page, type Locator } from '@playwright/test';
// Page Object Model for the "admin" page.
// Here you can add locators and helper methods specific to the admin page.
class AdminPage {
// Page signed in as "admin".
page: Page;
// Example locator pointing to "Welcome, Admin" greeting.
greeting: Locator;
constructor(page: Page) {
this.page = page;
this.greeting = page.locator('#greeting');
}
}
// Page Object Model for the "user" page.
// Here you can add locators and helper methods specific to the user page.
class UserPage {
// Page signed in as "user".
page: Page;
// Example locator pointing to "Welcome, User" greeting.
greeting: Locator;
constructor(page: Page) {
this.page = page;
this.greeting = page.locator('#greeting');
}
}
// Declare the types of your fixtures.
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();
},
});
// Import test with our new fixtures.
import { test, expect } from '../playwright/fixtures';
// Use adminPage and userPage fixtures in the test.
test('admin and user', async ({ adminPage, userPage }) => {
// ... interact with both adminPage and userPage ...
await expect(adminPage.greeting).toHaveText('Welcome, Admin');
await expect(userPage.greeting).toHaveText('Welcome, User');
}); Хранилище сеанса
Использование аутентифицированного состояния охватывает cookies и локальное хранилище, используемое для авторизации. В редких случаях используется хранилище сеанса для хранения информации, связанной с состоянием авторизации. Хранилище сеанса специфично для определённого домена и не сохраняется при переходе на другие страницы. Playwright не предоставляет API для сохранения хранилища сеанса, но следующий фрагмент кода может использоваться для сохранения/загрузки хранилища сеанса.
// Get session storage and store as env variable
const sessionStorage = await page.evaluate(() => JSON.stringify(sessionStorage));
fs.writeFileSync('playwright/.auth/session.json', sessionStorage, 'utf-8');
// Set session storage in a new context
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); Избегайте авторизации в некоторых тестах
Вы можете сбросить состояние хранилища в файле теста, чтобы избежать авторизации, которая была установлена для всего проекта.
import { test } from '@playwright/test';
// Reset storage state for this file to avoid being authenticated
test.use({ storageState: { cookies: [], origins: [] } });
test('not signed in test', async ({ page }) => {
// ...
});
© 2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://playwright.dev/docs/auth