Фикстуры
Введение
Playwright Test основано на концепции фикстур. Фикстуры используются для создания среды для каждого теста, предоставляя тесту все необходимое и ничего лишнего. Фикстуры изолированы между тестами. С фикстурами вы можете группировать тесты по их смыслу, а не по общему настройке.
Встроенные фикстуры
Вы уже использовали фикстуры в своём первом тесте.
import { test, expect } from '@playwright/test';
test('basic test', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
}); Аргумент { page } указывает Playwright Test на настройку фикстуры page и предоставление её вашей функции теста.
Вот список предопределённых фикстур, которые вы, скорее всего, будете использовать чаще всего:
| Фикстура | Тип | Описание |
|---|---|---|
| page | Page | Изолированная страница для этого запуска теста. |
| context | BrowserContext | Изолированный контекст для этого запуска теста. Фикстура page также принадлежит этому контексту. Узнайте, как настроить контекст. |
| browser | Браузер | Браузеры используются совместно между тестами для оптимизации ресурсов. Узнайте, как настроить браузер. |
| browserName | строка | Название браузера, в котором в данный момент выполняется тест. Это может быть chromium, firefox или webkit. |
| request | APIRequestContext | Изолированный экземпляр APIRequestContext для этого запуска теста. |
Без фикстур
Вот как типичная настройка среды тестирования отличается между традиционным стилем тестирования и основанным на фикстурах.
TodoPage — это класс, который помогает взаимодействовать со страницей "списка дел" веб-приложения, следуя паттерну Page Object Model. Он использует Playwright's page внутри.
Нажмите, чтобы развернуть код для TodoPageimport type { Page, Locator } from '@playwright/test'; export class TodoPage { private readonly inputBox: Locator; private readonly todoItems: Locator; constructor(public readonly page: Page) { this.inputBox = this.page.locator('input.new-todo'); this.todoItems = this.page.getByTestId('todo-item'); } async goto() { await this.page.goto('https://demo.playwright.dev/todomvc/'); } async addToDo(text: string) { await this.inputBox.fill(text); await this.inputBox.press('Enter'); } async remove(text: string) { const todo = this.todoItems.filter({ hasText: text }); await todo.hover(); await todo.getByLabel('Delete').click(); } async removeAll() { while ((await this.todoItems.count()) > 0) { await this.todoItems.first().hover(); await this.todoItems.getByLabel('Delete').first().click(); } } }
const { test } = require('@playwright/test');
const { TodoPage } = require('./todo-page');
test.describe('todo tests', () => {
let todoPage;
test.beforeEach(async ({ page }) => {
todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
});
test.afterEach(async () => {
await todoPage.removeAll();
});
test('should add an item', async () => {
await todoPage.addToDo('my item');
// ...
});
test('should remove an item', async () => {
await todoPage.remove('item1');
// ...
});
}); С фикстурами
Фикстуры имеют ряд преимуществ по сравнению с перед/после хуками:
- Фикстуры капсулируют настройку и завершение в одном месте, что упрощает написание.
- Фикстуры многократно используемые между файлами тестов — вы можете определить их один раз и использовать во всех своих тестах. Именно так работает встроенная фикстура Playwright
page. - Фикстуры по требованию — вы можете определить столько фикстур, сколько захотите, и Playwright Test настроит только те, которые необходимы вашему тесту, и ничего лишнего.
- Фикстуры составные — они могут зависеть друг от друга для предоставления сложного поведения.
- Фикстуры гибкие. Тесты могут использовать любые комбинации фикстур для настройки точной среды, необходимой им, без влияния на другие тесты.
- Фикстуры упрощают группировку. Вам больше не нужно обёртывать тесты в
describeдля настройки среды, и вы можете свободно группировать свои тесты по их смыслу.
Нажмите, чтобы развернуть код для TodoPageimport type { Page, Locator } from '@playwright/test'; export class TodoPage { private readonly inputBox: Locator; private readonly todoItems: Locator; constructor(public readonly page: Page) { this.inputBox = this.page.locator('input.new-todo'); this.todoItems = this.page.getByTestId('todo-item'); } async goto() { await this.page.goto('https://demo.playwright.dev/todomvc/'); } async addToDo(text: string) { await this.inputBox.fill(text); await this.inputBox.press('Enter'); } async remove(text: string) { const todo = this.todoItems.filter({ hasText: text }); await todo.hover(); await todo.getByLabel('Delete').click(); } async removeAll() { while ((await this.todoItems.count()) > 0) { await this.todoItems.first().hover(); await this.todoItems.getByLabel('Delete').first().click(); } } }
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
// Extend basic test by providing a "todoPage" fixture.
const test = base.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
await use(todoPage);
await todoPage.removeAll();
},
});
test('should add an item', async ({ todoPage }) => {
await todoPage.addToDo('my item');
// ...
});
test('should remove an item', async ({ todoPage }) => {
await todoPage.remove('item1');
// ...
}); Создание фикстуры
Чтобы создать свою собственную фикстуру, используйте test.extend(), чтобы создать новый test объект, который будет её содержать.
Ниже мы создаём две фикстуры todoPage и settingsPage , которые следуют паттерну Page Object Model.
Нажмите, чтобы развернуть код для TodoPageиSettingsPageimport type { Page, Locator } from '@playwright/test'; export class TodoPage { private readonly inputBox: Locator; private readonly todoItems: Locator; constructor(public readonly page: Page) { this.inputBox = this.page.locator('input.new-todo'); this.todoItems = this.page.getByTestId('todo-item'); } async goto() { await this.page.goto('https://demo.playwright.dev/todomvc/'); } async addToDo(text: string) { await this.inputBox.fill(text); await this.inputBox.press('Enter'); } async remove(text: string) { const todo = this.todoItems.filter({ hasText: text }); await todo.hover(); await todo.getByLabel('Delete').click(); } async removeAll() { while ((await this.todoItems.count()) > 0) { await this.todoItems.first().hover(); await this.todoItems.getByLabel('Delete').first().click(); } } }SettingsPage похожа:
import type { Page } from '@playwright/test'; export class SettingsPage { constructor(public readonly page: Page) { } async switchToDarkMode() { // ... } }
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
import { SettingsPage } from './settings-page';
// Declare the types of your fixtures.
type MyFixtures = {
todoPage: TodoPage;
settingsPage: SettingsPage;
};
// Extend base test by providing "todoPage" and "settingsPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
export const test = base.extend<MyFixtures>({
todoPage: async ({ page }, use) => {
// Set up the fixture.
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
// Use the fixture value in the test.
await use(todoPage);
// Clean up the fixture.
await todoPage.removeAll();
},
settingsPage: async ({ page }, use) => {
await use(new SettingsPage(page));
},
});
export { expect } from '@playwright/test'; примечаниеИмена пользовательских фикстур должны начинаться с буквы или нижнего подчёркивания и могут содержать только буквы, цифры и нижние подчёркивания.
Использование фикстуры
Просто укажите фикстуру в аргументе вашей функции теста, и система запуска тестов позаботится о ней. Фикстуры также доступны в хуках и других фикстурах. Если вы используете TypeScript, фикстуры будут иметь правильный тип.
Ниже мы используем фикстуры todoPage и settingsPage , определённые выше.
import { test, expect } from './my-test';
test.beforeEach(async ({ settingsPage }) => {
await settingsPage.switchToDarkMode();
});
test('basic test', async ({ todoPage, page }) => {
await todoPage.addToDo('something nice');
await expect(page.getByTestId('todo-title')).toContainText(['something nice']);
}); Переопределение фикстур
В дополнение к созданию своих собственных фикстур, вы также можете переопределить существующие фикстуры, чтобы они соответствовали вашим потребностям. Рассмотрим следующий пример, который переопределяет фикстуру page путём автоматического перехода к определённой baseURL:
import { test as base } from '@playwright/test';
export const test = base.extend({
page: async ({ baseURL, page }, use) => {
await page.goto(baseURL);
await use(page);
},
}); Обратите внимание, что в этом примере фикстура page может зависеть от других встроенных фикстур, таких как testOptions.baseURL. Теперь мы можем настроить baseURL в файле конфигурации или локально в файле теста с помощью test.use().
test.use({ baseURL: 'https://playwright.dev' }); Фикстуры также можно переопределить, полностью заменив базовая фикстуру чем-то другим. Например, мы можем переопределить фикстуру testOptions.storageState для предоставления наших собственных данных.
import { test as base } from '@playwright/test';
export const test = base.extend({
storageState: async ({}, use) => {
const cookie = await getAuthCookie();
await use({ cookies: [cookie] });
},
}); Фикстуры, связанные с рабочим процессом
Playwright Test использует процессы-рабочие потоки для запуска файлов тестов. Аналогично тому, как фикстуры тестов настраиваются для отдельных запусков, фикстуры рабочих потоков настраиваются для каждого процесса-рабочего потока. Именно там вы можете настроить службы, запустить серверы и т. д. Playwright Test будет повторно использовать процесс-рабочий поток для как можно большего количества файлов тестов, при условии, что их фикстуры-рабочие потоки совпадают, и, следовательно, среды идентичны.
Ниже мы создадим фикстуру account , которая будет использоваться всеми тестами в одном рабочем потоке, и переопределим фикстуру page для входа в эту учётную запись для каждого теста. Чтобы генерировать уникальные учётные записи, мы будем использовать workerInfo.workerIndex, который доступен для любого теста или фикстуры. Обратите внимание на синтаксис в стиле кортежа для фикстуры рабочего потока — нам нужно передать {scope: 'worker'} , чтобы система запуска тестов настраивала эту фикстуру один раз на процесс-рабочий поток.
import { test as base } from '@playwright/test';
type Account = {
username: string;
password: string;
};
// Note that we pass worker fixture types as a second template parameter.
export const test = base.extend<{}, { account: Account }>({
account: [async ({ browser }, use, workerInfo) => {
// Unique username.
const username = 'user' + workerInfo.workerIndex;
const password = 'verysecure';
// Create the account with Playwright.
const page = await browser.newPage();
await page.goto('/signup');
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByText('Sign up').click();
// Make sure everything is ok.
await expect(page.getByTestId('result')).toHaveText('Success');
// Do not forget to cleanup.
await page.close();
// Use the account value.
await use({ username, password });
}, { scope: 'worker' }],
page: async ({ page, account }, use) => {
// Sign in with our account.
const { username, password } = account;
await page.goto('/signin');
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByText('Sign in').click();
await expect(page.getByTestId('userinfo')).toHaveText(username);
// Use signed-in page in the test.
await use(page);
},
});
export { expect } from '@playwright/test'; Автоматические фикстуры
Автоматические фикстуры настраиваются для каждого теста/рабочего потока, даже если тест не указывает их напрямую. Чтобы создать автоматическую фикстуру, используйте синтаксис кортежа и передайте { auto: true }.
Вот пример фикстуры, которая автоматически подключает отладочные журналы, когда тест завершается неудачей, поэтому мы можем позже просмотреть журналы в отчётчике. Обратите внимание, как она использует объект TestInfo, который доступен в каждом тесте/фикстуре для получения метаданных о выполняемом тесте.
import * as debug from 'debug';
import * as fs from 'fs';
import { test as base } from '@playwright/test';
export const test = base.extend<{ saveLogs: void }>({
saveLogs: [async ({}, use, testInfo) => {
// Collecting logs during the test.
const logs = [];
debug.log = (...args) => logs.push(args.map(String).join(''));
debug.enable('myserver');
await use();
// After the test we can check whether the test passed or failed.
if (testInfo.status !== testInfo.expectedStatus) {
// outputPath() API guarantees a unique file name.
const logFile = testInfo.outputPath('logs.txt');
await fs.promises.writeFile(logFile, logs.join('\n'), 'utf8');
testInfo.attachments.push({ name: 'logs', contentType: 'text/plain', path: logFile });
}
}, { auto: true }],
});
export { expect } from '@playwright/test'; Таймаут фикстуры
По умолчанию фикстура разделяет таймаут с тестом. Однако для медленных фикстур, особенно фикстур, связанных с рабочим потоком, удобно иметь отдельный таймаут. Таким образом, вы можете поддерживать небольшой общий таймаут теста и предоставлять медленной фикстуре больше времени.
import { test as base, expect } from '@playwright/test';
const test = base.extend<{ slowFixture: string }>({
slowFixture: [async ({}, use) => {
// ... perform a slow operation ...
await use('hello');
}, { timeout: 60000 }]
});
test('example test', async ({ slowFixture }) => {
// ...
}); Параметры фикстур
Playwright Test поддерживает запуск нескольких проектов тестов, которые можно настроить отдельно. Вы можете использовать фикстуры "options", чтобы сделать ваши параметры конфигурации декларативными и с проверкой типов. Узнайте больше о параметризации тестов.
Ниже мы создадим параметр defaultItem в дополнение к фикстуре todoPage из других примеров. Этот параметр будет установлен в файле конфигурации. Обратите внимание на синтаксис кортежа и аргумент { option: true }.
Нажмите, чтобы развернуть код для TodoPageimport type { Page, Locator } from '@playwright/test'; export class TodoPage { private readonly inputBox: Locator; private readonly todoItems: Locator; constructor(public readonly page: Page) { this.inputBox = this.page.locator('input.new-todo'); this.todoItems = this.page.getByTestId('todo-item'); } async goto() { await this.page.goto('https://demo.playwright.dev/todomvc/'); } async addToDo(text: string) { await this.inputBox.fill(text); await this.inputBox.press('Enter'); } async remove(text: string) { const todo = this.todoItems.filter({ hasText: text }); await todo.hover(); await todo.getByLabel('Delete').click(); } async removeAll() { while ((await this.todoItems.count()) > 0) { await this.todoItems.first().hover(); await this.todoItems.getByLabel('Delete').first().click(); } } }
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
// Declare your options to type-check your configuration.
export type MyOptions = {
defaultItem: string;
};
type MyFixtures = {
todoPage: TodoPage;
};
// Specify both option and fixture types.
export const test = base.extend<MyOptions & MyFixtures>({
// Define an option and provide a default value.
// We can later override it in the config.
defaultItem: ['Something nice', { option: true }],
// Our "todoPage" fixture depends on the option.
todoPage: async ({ page, defaultItem }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo(defaultItem);
await use(todoPage);
await todoPage.removeAll();
},
});
export { expect } from '@playwright/test'; Теперь мы можем использовать фикстуру todoPage как обычно и установить параметр defaultItem в файле конфигурации.
import { defineConfig } from '@playwright/test';
import type { MyOptions } from './my-test';
export default defineConfig<MyOptions>({
projects: [
{
name: 'shopping',
use: { defaultItem: 'Buy milk' },
},
{
name: 'wellbeing',
use: { defaultItem: 'Exercise!' },
},
]
}); Массив как значение параметра
Если значение вашего параметра является массивом, например, [{ name: 'Alice' }, { name: 'Bob' }], вам нужно будет обернуть его в дополнительный массив при предоставлении значения. Это лучше всего проиллюстрировано на примере.
type Person = { name: string };
const test = base.extend<{ persons: Person[] }>({
// Declare the option, default value is an empty array.
persons: [[], { option: true }],
});
// Option value is an array of persons.
const actualPersons = [{ name: 'Alice' }, { name: 'Bob' }];
test.use({
// CORRECT: Wrap the value into an array and pass the scope.
persons: [actualPersons, { scope: 'test' }],
});
test.use({
// WRONG: passing an array value directly will not work.
persons: actualPersons,
}); Порядок выполнения
Каждая фикстура имеет фазу настройки и завершения, разделённые вызовом await use() в фикстуре. Настройка выполняется до использования фикстуры тестом/хуком, а завершение выполняется, когда фикстура больше не будет использоваться тестом/хуком.
Фикстуры следуют этим правилам для определения порядка выполнения:
- Когда фикстура A зависит от фикстуры B: B всегда устанавливается до A и разворачивается после A.
- Неавтоматические фикстуры выполняются лениво, только тогда, когда тест/хук их нуждается.
- Фикстуры, связанные с тестом, разворачиваются после каждого теста, а фикстуры, связанные с работником, разворачиваются только при завершении процесса работника, выполняющего тесты.
Рассмотрим следующий пример:
import { test as base } from '@playwright/test';
const test = base.extend<{
testFixture: string,
autoTestFixture: string,
unusedFixture: string,
}, {
workerFixture: string,
autoWorkerFixture: string,
}>({
workerFixture: [async ({ browser }) => {
// workerFixture setup...
await use('workerFixture');
// workerFixture teardown...
}, { scope: 'worker' }],
autoWorkerFixture: [async ({ browser }) => {
// autoWorkerFixture setup...
await use('autoWorkerFixture');
// autoWorkerFixture teardown...
}, { scope: 'worker', auto: true }],
testFixture: [async ({ page, workerFixture }) => {
// testFixture setup...
await use('testFixture');
// testFixture teardown...
}, { scope: 'test' }],
autoTestFixture: [async () => {
// autoTestFixture setup...
await use('autoTestFixture');
// autoTestFixture teardown...
}, { scope: 'test', auto: true }],
unusedFixture: [async ({ page }) => {
// unusedFixture setup...
await use('unusedFixture');
// unusedFixture teardown...
}, { scope: 'test' }],
});
test.beforeAll(async () => { /* ... */ });
test.beforeEach(async ({ page }) => { /* ... */ });
test('first test', async ({ page }) => { /* ... */ });
test('second test', async ({ testFixture }) => { /* ... */ });
test.afterEach(async () => { /* ... */ });
test.afterAll(async () => { /* ... */ }); Обычно, если все тесты пройдены и не было выброшено никаких ошибок, порядок выполнения следующий.
- Настройка работника и
beforeAllраздел:-
browserнастройка, так как она требуетсяautoWorkerFixture. -
autoWorkerFixtureнастройка, так как автоматические фикстуры работника всегда устанавливаются до всего остального. -
beforeAllвыполняется.
-
-
first testраздел:-
autoTestFixtureнастройка, так как автоматические фикстуры теста всегда устанавливаются перед тестом иbeforeEachхуками. -
pageнастройка, так как она требуется вbeforeEachхуке. -
beforeEachвыполняется. -
first testвыполняется. -
afterEachвыполняется. -
pageразворачивание, так как это фикстура, связанная с тестом, и должна быть развернута после завершения теста. -
autoTestFixtureразворачивание, так как это фикстура, связанная с тестом, и должна быть развернута после завершения теста.
-
-
second testраздел:-
autoTestFixtureнастройка, так как автоматические фикстуры теста всегда устанавливаются перед тестом иbeforeEachхуками. -
pageнастройка, так как она требуется вbeforeEachхуке. -
beforeEachвыполняется. -
workerFixtureнастройка, так как она требуетсяtestFixture, которая требуетсяsecond test. -
testFixtureнастройка, так как она требуетсяsecond test. -
second testвыполняется. -
afterEachвыполняется. -
testFixtureразворачивание, так как это фикстура, связанная с тестом, и должна быть развернута после завершения теста. -
pageразворачивание, так как это фикстура, связанная с тестом, и должна быть развернута после завершения теста. -
autoTestFixtureразворачивание, так как это фикстура, связанная с тестом, и должна быть развернута после завершения теста.
-
-
afterAllи раздел разворачивания работника:-
afterAllвыполняется. -
workerFixtureразворачивание, так как это фикстура, связанная с работником, и должна быть развернута один раз в конце. -
autoWorkerFixtureразворачивание, так как это фикстура, связанная с работником, и должна быть развернута один раз в конце. -
browserразворачивание, так как это фикстура, связанная с работником, и должна быть развернута один раз в конце.
-
Несколько наблюдений:
-
pageиautoTestFixtureустанавливаются и разворачиваются для каждого теста, как фикстуры, связанные с тестом. -
unusedFixtureникогда не устанавливается, так как не используется ни одним тестом/хуком. -
testFixtureзависит отworkerFixtureи вызывает ее настройку. -
workerFixtureлениво устанавливается перед вторым тестом, но разворачивается один раз во время завершения работы работника, как фикстура, связанная с работником. -
autoWorkerFixtureустанавливается дляbeforeAllхука, ноautoTestFixtureнет.
Объединение пользовательских фикстур из нескольких модулей
Вы можете объединять фикстуры тестов из нескольких файлов или модулей:
import { mergeTests } from '@playwright/test';
import { test as dbTest } from 'database-test-utils';
import { test as a11yTest } from 'a11y-test-utils';
export const test = mergeTests(dbTest, a11yTest); import { test } from './fixtures';
test('passes', async ({ database, page, a11y }) => {
// use database and a11y fixtures.
}); Фикстуры в блоке
Обычно пользовательские фикстуры отображаются как отдельные шаги в режиме пользовательского интерфейса, Просмотр трассировки и различных отчетах о тестировании. Они также появляются в сообщениях об ошибках от исполнителя тестов. Для часто используемых фикстур это может означать много шума. Вы можете остановить отображение шагов фикстур в пользовательском интерфейсе, «упаковав» их.
import { test as base } from '@playwright/test';
export const test = base.extend({
helperFixture: [async ({}, use, testInfo) => {
// ...
}, { box: true }],
}); Это полезно для фикстур-помощников, которые не представляют интереса. Например, автоматическая фикстура, которая устанавливает некоторые общие данные, может быть безопасно скрыта от отчета о тестировании.
Пользовательское название фикстуры
Вместо обычного имени фикстуры, вы можете задать для фикстур пользовательское название, которое будет отображаться в отчетах о тестировании и сообщениях об ошибках.
import { test as base } from '@playwright/test';
export const test = base.extend({
innerFixture: [async ({}, use, testInfo) => {
// ...
}, { title: 'my fixture' }],
}); Добавление глобальных хуков beforeEach/afterEach
test.beforeEach() и test.afterEach() хуки выполняются до/после каждого теста, объявленного в том же файле и в том же блоке test.describe() (если таковой имеется). Если вы хотите объявить хуки, которые выполняются до/после каждого теста глобально, вы можете объявить их как автоматические фикстуры следующим образом:
import { test as base } from '@playwright/test';
export const test = base.extend<{ forEachTest: void }>({
forEachTest: [async ({ page }, use) => {
// This code runs before every test.
await page.goto('http://localhost:8000');
await use();
// This code runs after every test.
console.log('Last URL:', page.url());
}, { auto: true }], // automatically starts for every test.
}); И затем импортируйте фикстуры во все свои тесты:
import { test } from './fixtures';
import { expect } from '@playwright/test';
test('basic', async ({ page }) => {
expect(page).toHaveURL('http://localhost:8000');
await page.goto('https://playwright.dev');
}); Добавление глобальных хуков beforeAll/afterAll
test.beforeAll() и test.afterAll() хуки выполняются до/после всех тестов, объявленных в том же файле и в том же блоке test.describe(), один раз на процесс работника. Если вы хотите объявить хуки, которые выполняются до/после всех тестов во всех файлах, вы можете объявить их как автоматические фикстуры с scope: 'worker' следующим образом:
import { test as base } from '@playwright/test';
export const test = base.extend<{}, { forEachWorker: void }>({
forEachWorker: [async ({}, use) => {
// This code runs before all the tests in the worker process.
console.log(`Starting test worker ${test.info().workerIndex}`);
await use();
// This code runs after all the tests in the worker process.
console.log(`Stopping test worker ${test.info().workerIndex}`);
}, { scope: 'worker', auto: true }], // automatically starts for every worker.
}); И затем импортируйте фикстуры во все свои тесты:
import { test } from './fixtures';
import { expect } from '@playwright/test';
test('basic', async ({ }) => {
// ...
}); Обратите внимание, что фикстуры все равно будут выполняться один раз на каждый процесс работника, но вам не нужно переопределять их в каждом файле.
© 2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://playwright.dev/docs/test-fixtures