QA Interview Hub · База знань · Тести

Патерни

Strategy

Strategy: змінна поведінка за спільним інтерфейсом

Strategy визначає сімейство взаємозамінних алгоритмів за одним інтерфейсом, і вибір робиться під час виконання. У тестовому фреймворку типові приклади: різні способи логіну (UI, API, SSO), різні платіжні методи, генерація даних для різних регіонів.

interface LoginStrategy {
  login(page: Page, user: User): Promise<void>;
}

class UiLogin implements LoginStrategy {
  async login(page: Page, user: User) {
    await page.goto('/login');
    await page.getByLabel('Email').fill(user.email);
    await page.getByLabel('Password').fill(user.password);
    await page.getByRole('button', { name: 'Sign in' }).click();
  }
}

class ApiLogin implements LoginStrategy {
  async login(page: Page, user: User) {
    const res = await page.request.post('/api/login', { data: user });
    expect(res.ok()).toBeTruthy();
  }
}

page.request ділить cookies з контекстом сторінки, тому після API-логіну сторінка вже автентифікована (якщо сесія тримається в cookies, а не в localStorage).

Вибір стратегії можна зробити через опцію фікстури, яку задає test.use або проєкт у конфігу. Тоді тест не знає, як саме відбувся логін.

Перевага перед if/else або switch: новий спосіб додається новим класом, без змін у тестах і в наявних стратегіях (Open/Closed).

На співбесіді: Strategy я використовую, коли є кілька взаємозамінних способів зробити одне й те саме, наприклад логін через UI або API. Тест працює з інтерфейсом, а конкретна реалізація обирається через опцію фікстури.

Опції фікстур як Strategy і коли патерн зайвий

У Playwright Test є готовий механізм для вибору поведінки: option-фікстури. Значення задається в projects або через test.use.

type Opts = { loginMode: 'ui' | 'api' };

export const test = base.extend<Opts & { loggedIn: void }>({
  loginMode: ['api', { option: true }],
  loggedIn: async ({ page, loginMode }, use) => {
    const strategy = loginMode === 'ui' ? new UiLogin() : new ApiLogin();
    await strategy.login(page, defaultUser);
    await use();
  },
});
test.describe('login flow', () => {
  test.use({ loginMode: 'ui' });
  // ...
});

Це поєднання Strategy і DI: тест залежить від loggedIn, а конкретна стратегія підставляється конфігурацією.

Коли Strategy НЕ потрібен:

  • Варіантів лише два і нових не передбачається. Простий if у фікстурі чесніший за інтерфейс і три класи.
  • Різниця між "стратегіями" в одному параметрі. Тоді досить передати параметр.
  • Для різних браузерів і девайсів. Це вже робить сам Playwright через projects і devices, писати свою стратегію браузера не треба.

Типова помилка на співбесіді: назвати Strategy будь-який switch. Strategy про поліморфізм: різні реалізації одного контракту, які можна підміняти незалежно.

На співбесіді: У Playwright я реалізую Strategy через option-фікстуру: значення приходить з projects або test.use, а фікстура обирає реалізацію. Якщо варіантів два і більше не буде, обходжуся простим if.