Зачем тестировщикам навыки программирования

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

Мы используем JavaScript для примеров, так как это самый распространённый язык в современной автоматизации (Playwright, Cypress, WebdriverIO). Концепции применимы к любому языку.

Переменные и типы данных

Переменные хранят данные, которые используют ваши тесты — учётные данные, URL, ожидаемые значения, селекторы элементов.

Объявление переменных

// Используйте const для неизменяемых значений
const BASE_URL = 'https://app.example.com';
const TIMEOUT = 30000;

// Используйте let для изменяемых значений
let loginAttempts = 0;
let currentUser = null;

Типы данных

ТипПримерПрименение в тестировании
String'hello', "world"URL, селекторы, ожидаемый текст
Number42, 3.14Таймауты, счётчики, цены
Booleantrue, falseFeature-флаги, условия
NullnullПустые/отсутствующие значения
UndefinedundefinedНеинициализированные переменные
Array[1, 2, 3]Списки тестовых данных
Object{name: 'John'}Структурированные данные

Строковые операции для тестирования

// Частые строковые операции в тестах
const pageTitle = 'Welcome to Dashboard - MyApp';

// Проверка на содержание ожидаемого текста
pageTitle.includes('Dashboard'); // true

// Извлечение частей строк
pageTitle.toLowerCase(); // 'welcome to dashboard - myapp'
pageTitle.trim(); // Удаление пробелов

// Шаблонные строки для динамических значений
const url = `${BASE_URL}/users/${userId}/profile`;
const errorMsg = `Ожидалось ${expected}, но получено ${actual}`;

Условные конструкции

Условия позволяют тестам принимать решения на основе условий.

// Базовый if-else
if (userRole === 'admin') {
  await page.click('#admin-panel');
} else if (userRole === 'editor') {
  await page.click('#editor-tools');
} else {
  await page.click('#user-dashboard');
}

// Тернарный оператор для простых условий
const timeout = isCI ? 60000 : 30000;

// Проверка нескольких условий
if (statusCode >= 200 && statusCode < 300) {
  console.log('Запрос успешен');
} else if (statusCode === 404) {
  console.log('Ресурс не найден');
} else {
  console.log(`Неожиданный статус: ${statusCode}`);
}

Операторы сравнения

ОператорЗначениеПример
===Строгое равенствоstatus === 200
!==Не равноrole !== 'guest'
>, <Больше/меньшеcount > 0
>=, <=Больше/меньше или равноprice >= 9.99
&&ИisVisible && isEnabled
||ИЛИisAdmin || isSuperuser

Циклы

Циклы повторяют действия — незаменимы для data-driven тестирования и пакетных операций.

Цикл for

// Тестирование логина с несколькими пользователями
const users = ['admin', 'editor', 'viewer'];

for (let i = 0; i < users.length; i++) {
  console.log(`Тестируем пользователя: ${users[i]}`);
}

// Современный цикл for...of (предпочтительный)
for (const user of users) {
  console.log(`Тестируем пользователя: ${user}`);
}

forEach и map

// Обработка каждого результата теста
const results = [
  { test: 'login', status: 'pass' },
  { test: 'checkout', status: 'fail' },
  { test: 'search', status: 'pass' }
];

// forEach - выполнить действие с каждым элементом
results.forEach(result => {
  console.log(`${result.test}: ${result.status}`);
});

// map - трансформировать каждый элемент
const testNames = results.map(r => r.test);
// ['login', 'checkout', 'search']

// filter - оставить элементы по условию
const failures = results.filter(r => r.status === 'fail');
// [{ test: 'checkout', status: 'fail' }]

Функции

Функции — строительные блоки чистого тест-кода. Они инкапсулируют переиспользуемую логику.

Базовые функции

// Объявление функции
function calculateTotal(price, quantity, taxRate) {
  const subtotal = price * quantity;
  const tax = subtotal * taxRate;
  return subtotal + tax;
}

// Arrow-функция (современный синтаксис)
const calculateTotal = (price, quantity, taxRate) => {
  const subtotal = price * quantity;
  const tax = subtotal * taxRate;
  return subtotal + tax;
};

// Использование функции
const total = calculateTotal(29.99, 3, 0.08);

Функции в автоматизации

// Переиспользуемая функция логина
async function login(page, username, password) {
  await page.goto('/login');
  await page.fill('#username', username);
  await page.fill('#password', password);
  await page.click('#submit');
  await page.waitForURL('/dashboard');
}

// Переиспользуемый хелпер проверки
function assertInRange(actual, min, max) {
  if (actual < min || actual > max) {
    throw new Error(`${actual} не в диапазоне [${min}, ${max}]`);
  }
}

// Использование в тестах
await login(page, 'admin@test.com', 'password123');
assertInRange(responseTime, 0, 3000);

Коллекции: Массивы и объекты

Массивы

// Тестовые данные как массивы
const validEmails = ['user@test.com', 'admin@company.org', 'test+tag@gmail.com'];
const invalidEmails = ['', 'not-an-email', '@missing.com', 'no-domain@'];

// Полезные методы массивов
validEmails.length;              // 3
validEmails.includes('user@test.com'); // true
validEmails.push('new@test.com'); // Добавить элемент
validEmails.indexOf('admin@company.org'); // 1

Объекты

// Тестовый пользователь как объект
const testUser = {
  name: 'Jane Smith',
  email: 'jane@test.com',
  role: 'admin',
  permissions: ['read', 'write', 'delete']
};

// Доступ к свойствам
console.log(testUser.name);       // 'Jane Smith'
console.log(testUser['email']);   // 'jane@test.com'

// Селекторы как объект
const selectors = {
  loginForm: '#login-form',
  emailInput: '[data-testid="email"]',
  passwordInput: '[data-testid="password"]',
  submitButton: 'button[type="submit"]'
};

Async/Await

Автоматизация тестирования активно работает с асинхронностью — ожидание загрузки страниц, появления элементов, ответов API. Понимание async/await обязательно.

Проблема

// Без async/await - вложенные callbacks становятся нечитаемыми
page.goto('/login', function() {
  page.fill('#email', 'test@test.com', function() {
    page.click('#submit', function() {
      // Это "callback hell"
    });
  });
});

Решение: Async/Await

// С async/await - чисто и читаемо
async function testLogin(page) {
  await page.goto('/login');
  await page.fill('#email', 'test@test.com');
  await page.fill('#password', 'secret123');
  await page.click('#submit');
  await page.waitForSelector('.dashboard');
}

Ключевое слово await приостанавливает выполнение до завершения асинхронной операции. Ключевое слово async помечает функцию, содержащую вызовы await.

Распространённые асинхронные паттерны в тестировании

// Ожидание нескольких событий
const [response] = await Promise.all([
  page.waitForResponse('/api/users'),
  page.click('#load-users')
]);

// Ожидание с таймаутом
await page.waitForSelector('.modal', { timeout: 5000 });

// Паттерн повторных попыток
async function waitForElement(page, selector, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      await page.waitForSelector(selector, { timeout: 2000 });
      return true;
    } catch {
      console.log(`Попытка ${i + 1}/${retries}`);
    }
  }
  return false;
}

Обработка ошибок

Тесты падают. Хорошая обработка ошибок гарантирует понятные сообщения о сбоях и обязательную очистку.

Try-Catch

async function testCheckout(page) {
  try {
    await page.goto('/checkout');
    await page.fill('#card', '4242424242424242');
    await page.click('#pay');
    await expect(page.locator('.success')).toBeVisible();
  } catch (error) {
    console.error(`Тест checkout упал: ${error.message}`);
    await page.screenshot({ path: 'checkout-failure.png' });
    throw error; // Пробросить для пометки теста как упавшего
  }
}

Блок Finally

async function testWithCleanup(page) {
  let createdUserId;
  try {
    createdUserId = await createTestUser();
    await page.goto(`/users/${createdUserId}`);
    await expect(page.locator('.user-name')).toHaveText('Test User');
  } finally {
    // Очистка выполняется всегда, даже при падении теста
    if (createdUserId) {
      await deleteTestUser(createdUserId);
    }
  }
}

Практические паттерны для тестирования

Test Data Builder

function createUser(overrides = {}) {
  return {
    name: 'Default User',
    email: `user_${Date.now()}@test.com`,
    role: 'viewer',
    active: true,
    ...overrides
  };
}

// Использование
const admin = createUser({ role: 'admin', name: 'Admin User' });
const inactive = createUser({ active: false });

Управление конфигурацией

const config = {
  baseUrl: process.env.BASE_URL || 'http://localhost:3000',
  apiUrl: process.env.API_URL || 'http://localhost:8080',
  timeout: parseInt(process.env.TIMEOUT) || 30000,
  headless: process.env.CI === 'true'
};

Упражнение: Напишите свои первые хелпер-функции

Напишите следующие функции на JavaScript:

  1. generateEmail() — возвращает уникальный email для тестовых данных
  2. formatPrice(amount, currency) — форматирует цену как “$29.99” или “EUR 29.99”
  3. retryAction(action, maxRetries) — повторяет async-действие до maxRetries раз
  4. compareArrays(arr1, arr2) — возвращает true, если массивы содержат одинаковые элементы

Эти паттерны постоянно встречаются в реальных проектах автоматизации.

Ключевые выводы

  • Освойте переменные, функции, условия и циклы — это 80% тест-кода
  • Изучите async/await досконально — все современные фреймворки его используют
  • Используйте try-catch-finally для надёжной обработки ошибок и очистки
  • Объекты и массивы — основные структуры данных для тестовых данных
  • Функции должны быть маленькими, сфокусированными и переиспользуемыми