Автоматическое тестирование сайта: зачем и как

За 10 лет работы с сайтами я насмотрелся на самые разные косяки: от падения интернет-магазина в пятницу вечером до багов, которые обнаруживались только через месяц после релиза. И честно говоря, большинство из них можно было предотвратить с помощью автоматического тестирования.

Что такое автоматическое тестирование сайта

Автоматическое тестирование — это процесс проверки работоспособности сайта с помощью специальных программ без участия человека. Грубо говоря, вместо того чтобы каждый раз вручную кликать по кнопкам и формам, вы пишете сценарии, которые делают это за вас.

На моей практике был клиент с интернет-магазином на Bitrix, где после каждого обновления что-то ломалось. То корзина не работает, то форма заказа глючит. Мы внедрили автотесты — и количество багов на проде сократилось в разы.

Автотесты бывают разных уровней:

Для веб-сайтов чаще всего используются E2E-тесты и интеграционные. Они покрывают основные пользовательские сценарии: регистрацию, авторизацию, оформление заказа, отправку форм.

💡
Совет: Начинайте с тестирования самых критичных функций сайта. Для интернет-магазина это корзина и оформление заказа, для корпоративного сайта — формы обратной связи.

Почему автотесты необходимы

Ручное тестирование — это боль. Особенно когда у вас сложный сайт с кучей функций. Я помню проект на Laravel, где после каждого деплоя приходилось час тестировать админку вручную. А если что-то находили — откатывали и тестировали заново.

Основные проблемы ручного тестирования:

Автотесты решают эти проблемы кардинально. На том же Laravel-проекте мы настроили CI/CD pipeline с автотестами — теперь каждый коммит автоматически проверяется за 5 минут. Если что-то сломалось, сразу получаем уведомление в Slack.

Конкретные преимущества автотестирования:

Быстрая обратная связь. Вместо часа ручного тестирования — 5-10 минут автоматической проверки. У одного клиента мы сократили время тестирования с 2 часов до 15 минут.

Постоянная проверка. Автотесты можно запускать хоть каждый час. Особенно полезно для сайтов с частыми обновлениями контента.

Покрытие edge cases. Автотесты не забывают проверить редкие сценарии, которые человек может пропустить. Например, что происходит, если в корзину добавить товар с нулевой ценой.

Регрессионное тестирование. Автотесты гарантируют, что новый функционал не сломал старый. Это особенно критично для сложных проектов на Bitrix с множественными доработками.

⚠️
Важно: Автотесты не заменяют ручное тестирование полностью. UX, визуальные баги, удобство интерфейса — всё это нужно проверять глазами.

Виды автотестов для сайтов

За годы работы я перепробовал разные подходы к тестированию. Некоторые оказались полезными, другие — пустой тратой времени. Расскажу про те, которые реально работают в веб-разработке.

End-to-End тестирование

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

Популярные инструменты: Playwright, Cypress, Selenium. Лично я предпочитаю Playwright — быстрый, стабильный, хорошо работает с современными фреймворками.

// Пример E2E-теста для оформления заказа
import { test, expect } from '@playwright/test';

test('оформление заказа работает', async ({ page }) => {
  await page.goto('/catalog/');
  
  // Добавляем товар в корзину
  await page.click('[data-testid="add-to-cart-123"]');
  await expect(page.locator('.cart-counter')).toContainText('1');
  
  // Переходим в корзину
  await page.click('[data-testid="cart-link"]');
  await expect(page).toHaveURL('/cart/');
  
  // Оформляем заказ
  await page.fill('[name="name"]', 'Иван Иванов');
  await page.fill('[name="phone"]', '+7 900 123-45-67');
  await page.click('[type="submit"]');
  
  // Проверяем успешное оформление
  await expect(page.locator('.success-message')).toBeVisible();
});

API-тестирование

Если у вас есть API (а в современных сайтах оно есть почти всегда), его тоже нужно тестировать. API-тесты быстрые, стабильные и покрывают бизнес-логику без UI.

У меня был проект на WordPress с кастомным REST API для мобильного приложения. API-тесты помогли поймать кучу багов ещё до того, как их увидели мобильные разработчики.

// Тест API регистрации пользователя
test('POST /api/register создаёт пользователя', async ({ request }) => {
  const response = await request.post('/api/register', {
    data: {
      email: 'test@example.com',
      password: 'password123',
      name: 'Test User'
    }
  });
  
  expect(response.status()).toBe(201);
  const user = await response.json();
  expect(user.email).toBe('test@example.com');
  expect(user.id).toBeDefined();
});

Тесты производительности

Скорость работы сайта критично важна. Я использую автоматические тесты производительности, чтобы отслеживать деградацию скорости после изменений. Особенно полезно для оптимизации Core Web Vitals.

Инструменты: Lighthouse CI, WebPageTest API, K6 для нагрузочного тестирования.

Визуальное тестирование

Автоматическое сравнение скриншотов до и после изменений. Помогает поймать визуальные регрессии — когда вёрстка поехала, но функционально всё работает.

Честно говоря, визуальные тесты капризные. Малейшее изменение шрифта или цвета — и тест падает. Но для критичных страниц (главная, каталог товаров) они полезны.

ℹ️
Из практики: Начните с E2E-тестов для основных пользовательских сценариев. API-тесты добавляйте, если есть отдельный API. Визуальные тесты — только для критичных страниц и стабильного дизайна.

Инструменты для автотестирования

За годы работы я перепробовал десятки инструментов. Некоторые отлично подходят для простых сайтов, другие — только для сложных enterprise-проектов. Расскажу про те, которыми пользуюсь сам.

Playwright

Мой текущий фаворит для E2E-тестирования. Быстрый, стабильный, поддерживает все современные браузеры. Отлично работает с SPA на React/Vue и серверными приложениями на PHP.

Плюсы:

Минусы:

Cypress

Популярная альтернатива Playwright. Хороший DX (developer experience), много документации и примеров. Но работает медленнее и только в Chromium-браузерах.

Cypress отлично подходит для команд, которые только начинают с автотестирования. Простой синтаксис, наглядная отладка.

Selenium

Старожил среди инструментов автотестирования. Поддерживает все языки программирования, все браузеры. Но медленный и капризный в настройке.

Selenium стоит использовать, только если у вас специфичные требования — например, тестирование в старых браузерах или интеграция с существующей Java/C#-инфраструктурой.

PHPUnit для backend-тестирования

Если ваш сайт написан на PHP (Laravel, Bitrix, WordPress), PHPUnit — must-have для unit и интеграционных тестов.

 'regular']);
        
        $discount = $calculator->calculate($user, 1000);
        
        $this->assertEquals(50, $discount); // 5% скидка
    }
    
    public function testCalculateDiscountForVipUser()
    {
        $calculator = new DiscountCalculator();
        $user = new User(['type' => 'vip']);
        
        $discount = $calculator->calculate($user, 1000);
        
        $this->assertEquals(150, $discount); // 15% скидка
    }
}

Jest для JavaScript

Если у вас много фронтенд-логики на JavaScript, Jest покроет unit-тестирование. Особенно полезно для SPA и сложных форм с валидацией.

Lighthouse CI

Автоматическое тестирование производительности. Интегрируется с CI/CD, отслеживает метрики скорости сайта. У одного клиента мы настроили Lighthouse CI — теперь каждый деплой проверяется на скорость автоматически.

💡
Рекомендация: Для начала хватит одного инструмента E2E-тестирования (Playwright или Cypress) плюс PHPUnit для backend-логики. Остальное добавляйте по мере необходимости.

Настройка автотестов на практике

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

Настройка Playwright для сайта на Laravel

Допустим, у нас есть интернет-магазин на Laravel. Хотим автоматизировать тестирование основных пользовательских сценариев.

Сначала устанавливаем Playwright:

npm init playwright@latest
npx playwright install

Создаём базовый конфиг:

// playwright.config.js
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:8000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],

  webServer: {
    command: 'php artisan serve',
    url: 'http://localhost:8000',
    reuseExistingServer: !process.env.CI,
  },
});

Теперь пишем тест для регистрации пользователя:

// tests/e2e/registration.spec.js
import { test, expect } from '@playwright/test';

test.describe('Регистрация пользователя', () => {
  test('успешная регистрация с валидными данными', async ({ page }) => {
    await page.goto('/register');
    
    // Заполняем форму
    await page.fill('[name="name"]', 'Тест Тестов');
    await page.fill('[name="email"]', `test${Date.now()}@example.com`);
    await page.fill('[name="password"]', 'SecurePass123');
    await page.fill('[name="password_confirmation"]', 'SecurePass123');
    
    // Отправляем форму
    await page.click('button[type="submit"]');
    
    // Проверяем редирект на главную
    await expect(page).toHaveURL('/dashboard');
    
    // Проверяем, что пользователь авторизован
    await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
  });
  
  test('ошибка при невалидном email', async ({ page }) => {
    await page.goto('/register');
    
    await page.fill('[name="name"]', 'Тест Тестов');
    await page.fill('[name="email"]', 'invalid-email');
    await page.fill('[name="password"]', 'SecurePass123');
    await page.fill('[name="password_confirmation"]', 'SecurePass123');
    
    await page.click('button[type="submit"]');
    
    // Проверяем, что остались на странице регистрации
    await expect(page).toHaveURL('/register');
    
    // Проверяем сообщение об ошибке
    await expect(page.locator('.error-message')).toContainText('некорректный email');
  });
});

Интеграция с CI/CD

Автотесты бесполезны, если их не запускать автоматически. Я настраиваю их в GitHub Actions или GitLab CI.

Пример конфига для GitHub Actions:

# .github/workflows/tests.yml
name: E2E Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: testing
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.2
        extensions: mbstring, pdo_mysql
    
    - name: Install dependencies
      run: composer install --no-dev --optimize-autoloader
    
    - name: Setup Laravel
      run: |
        cp .env.testing .env
        php artisan key:generate
        php artisan migrate --force
        php artisan db:seed --force
    
    - name: Install Playwright
      run: |
        npm install
        npx playwright install chromium
    
    - name: Run E2E tests
      run: npx playwright test
    
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-results
        path: test-results/

Тестирование на разных окружениях

На практике сайт ведёт себя по-разному в development, staging и production. Я настраиваю тесты так, чтобы они работали на всех окружениях.

Использую переменные окружения для разных конфигов:

# .env.testing
APP_ENV=testing
APP_URL=http://localhost:8000
DB_DATABASE=testing

# .env.staging  
APP_ENV=staging
APP_URL=https://staging.example.com
DB_DATABASE=staging

# .env.production
APP_ENV=production  
APP_URL=https://example.com
DB_DATABASE=production

И адаптирую тесты под окружение:

// utils/config.js
export const config = {
  baseURL: process.env.BASE_URL || 'http://localhost:8000',
  isProduction: process.env.NODE_ENV === 'production',
  timeout: process.env.NODE_ENV === 'production' ? 30000 : 10000,
};

// tests/e2e/checkout.spec.js
import { config } from '../utils/config.js';

test('оформление заказа', async ({ page }) => {
  // На продакшене используем тестовые данные
  const testEmail = config.isProduction 
    ? 'test+automation@example.com'
    : 'test@example.com';
    
  await page.fill('[name="email"]', testEmail);
  // остальная логика теста...
});
⚠️
Осторожно с продакшеном: Никогда не запускайте тесты, которые изменяют данные, на боевом сервере. Используйте отдельную staging-среду или моки для внешних сервисов.

Тестирование сайтов на Bitrix

Bitrix — особенная CMS со своими нюансами. За годы работы с ней я выработал подходы к тестированию, которые реально работают.

Специфика тестирования Bitrix

Главная проблема Bitrix — сложная структура и множество зависимостей. Компоненты могут использовать кеширование, подключать внешние модули, обращаться к API. Простые unit-тесты здесь не очень эффективны.

Я фокусируюсь на интеграционных и E2E-тестах. Они покрывают реальные пользовательские сценарии с учётом всех особенностей Bitrix.

Настройка тестового окружения

Для Bitrix нужно отдельное тестовое окружение с копией БД. Я создаю отдельный dbconn.php для тестов:

И переключатель в зависимости от окружения:

Тестирование компонентов Bitrix

Для тестирования кастомных компонентов я использую PHPUnit с инициализацией ядра Bitrix:

Add([
            'NAME' => 'Тестовый каталог',
            'CODE' => 'test_catalog',
            'IBLOCK_TYPE_ID' => 'catalog',
            'SITE_ID' => 's1',
        ]);
        
        // Добавляем тестовый товар
        $element = new \CIBlockElement();
        $elementId = $element->Add([
            'IBLOCK_ID' => $iblockId,
            'NAME' => 'Тестовый товар',
            'ACTIVE' => 'Y',
            'PREVIEW_TEXT' => 'Описание товара',
        ]);
        
        // Тестируем компонент
        global $APPLICATION;
        ob_start();
        $APPLICATION->IncludeComponent(
            'custom:catalog.list',
            '',
            ['IBLOCK_ID' => $iblockId]
        );
        $output = ob_get_clean();
        
        $this->assertStringContainsString('Тестовый товар', $output);
        
        // Очищаем тестовые данные
        \CIBlockElement::Delete($elementId);
        \CIBlock::Delete($iblockId);
    }
}

E2E-тестирование интернет-магазина на Bitrix

Для полноценного тестирования интернет-магазина я использую Playwright с учётом особенностей Bitrix:

// tests/e2e/bitrix-shop.spec.js
import { test, expect } from '@playwright/test';

test.describe('Интернет-магазин Bitrix', () => {
  test.beforeEach(async ({ page }) => {
    // Bitrix может долго грузиться
    page.setDefaultTimeout(30000);
  });
  
  test('добавление товара в корзину', async ({ page }) => {
    await page.goto('/catalog/');
    
    // Ждём загрузки каталога (Bitrix может подгружать компоненты)
    await page.waitForSelector('.catalog-item', { timeout: 10000 });
    
    // Кликаем на первый товар
    await page.click('.catalog-item:first-child .btn-buy');
    
    // Ждём обновления корзины (AJAX)
    await page.waitForFunction(() => {
      const counter = document.querySelector('.basket-counter');
      return counter && parseInt(counter.textContent) > 0;
    });
    
    // Проверяем, что товар добавился
    await expect(page.locator('.basket-counter')).toContainText('1');
  });
  
  test('оформление заказа', async ({ page }) => {
    // Добавляем товар в корзину
    await page.goto('/catalog/');
    await page.click('.catalog-item:first-child .btn-buy');
    await page.waitForSelector('.basket-counter:has-text("1")');
    
    // Переходим в корзину
    await page.click('.basket-link');
    await expect(page).toHaveURL(/\/personal\/cart\//);
    
    // Оформляем заказ
    await page.click('.btn-order');
    await page.fill('[name="ORDER_PROP_1"]', 'Тест Тестович'); // ФИО
    await page.fill('[name="ORDER_PROP_2"]', 'test@example.com'); // Email  
    await page.fill('[name="ORDER_PROP_3"]', '+7 900 123-45-67'); // Телефон
    
    await page.click('.btn-order-submit');
    
    // Проверяем успешное оформление
    await expect(page.locator('.order-success')).toBeVisible();
    await expect(page.locator('.order-number')).toContainText(/№\s*\d+/);
  });
});

Важный нюанс: Bitrix активно использует AJAX и может долго инициализироваться. Поэтому я всегда увеличиваю таймауты и добавляю explicit waits.

Также полезно тестировать кеширование в Bitrix — проверять, что после очистки кеша сайт работает корректно.

Тестирование сайтов на WordPress

WordPress — совершенно другая история. Здесь много готовых плагинов, но каждый может сломаться после обновления. Плюс кастомные темы с собственной логикой.

Unit-тестирование WordPress

WordPress предоставляет собственную тестовую среду на базе PHPUnit. Честно говоря, настройка муторная, но результат того стоит.

# Установка WP test suite
bash bin/install-wp-tests.sh wordpress_test root 'password' localhost latest

Пример теста для кастомного плагина:

assertStringContainsString('assertStringContainsString('id="custom-form-123"', $output);
    }
    
    public function test_ajax_handler() {
        // Тестируем AJAX-обработчик
        $_POST['action'] = 'custom_form_submit';
        $_POST['form_data'] = 'name=John&email=john@example.com';
        
        try {
            $this->_handleAjax('custom_form_submit');
        } catch (WPAjaxDieContinueException $e) {
            // AJAX завершился корректно
        }
        
        $response = json_decode($this->_last_response, true);
        $this->assertEquals('success', $response['status']);
    }
}

E2E-тестирование WordPress

Для E2E-тестирования WordPress я использую Playwright с учётом особенностей админки:

// tests/e2e/wordpress-admin.spec.js
import { test, expect } from '@playwright/test';

test.describe('Админка WordPress', () => {
  test.beforeEach(async ({ page }) => {
    // Авторизуемся в админке
    await page.goto('/wp-admin/');
    await page.fill('#user_login', 'admin');
    await page.fill('#user_pass', 'password');
    await page.click('#wp-submit');
    
    await expect(page).toHaveURL(/wp-admin\/(?:index\.php)?$/);
  });
  
  test('создание нового поста', async ({ page }) => {
    // Переходим к созданию поста
    await page.click('text=Записи');
    await page.click('text=Добавить новую');
    
    // Заполняем пост
    await page.fill('[data-testid="post-title"]', 'Тестовый пост');
    
    // Работаем с Gutenberg редактором
    await page.click('[data-type="core/paragraph"]');
    await page.keyboard.type('Содержимое тестового поста');
    
    // Публикуем
    await page.click('text=Опубликовать');
    await page.click('text=Опубликовать', { nth: 1 }); // подтверждение
    
    // Проверяем успешную публикацию
    await expect(page.locator('text=Запись опубликована')).toBeVisible();
    
    // Проверяем на фронтенде
    await page.click('text=Просмотреть запись');
    await expect(page.locator('h1')).toContainText('Тестовый пост');
  });
  
  test('обновление плагина', async ({ page }) => {
    await page.goto('/wp-admin/plugins.php');
    
    // Ищем плагин с обновлением
    const updateLink = page.locator('.update-now').first();
    if (await updateLink.count() > 0) {
      await updateLink.click();
      
      // Ждём завершения обновления
      await expect(page.locator('.notice-success')).toBeVisible({ timeout: 30000 });
      
      // Проверяем, что сайт работает
      await page.goto('/');
      await expect(page.locator('body')).toBeVisible();
    }
  });
});

Тестирование WooCommerce

Если у вас интернет-магазин на WooCommerce, критично важно тестировать процесс покупки:

// tests/e2e/woocommerce.spec.js
test('покупка товара в WooCommerce', async ({ page }) => {
  // Добавляем товар в корзину
  await page.goto('/shop/');
  await page.click('.product:first-child .add_to_cart_button');
  
  // Ждём добавления (WooCommerce использует AJAX)
  await page.waitForSelector('.cart-contents-count:not(:empty)');
  
  // Переходим в корзину
  await page.click('.cart-contents');
  await expect(page).toHaveURL(/\/cart\//);
  
  // Оформляем заказ
  await page.click('.checkout-button');
  
  // Заполняем данные покупателя
  await page.fill('#billing_first_name', 'Иван');
  await page.fill('#billing_last_name', 'Петров');
  await page.fill('#billing_email', 'ivan@example.com');
  await page.fill('#billing_phone', '+7 900 123-45-67');
  
  // Выбираем способ оплаты
  await page.check('#payment_method_cod'); // наложенный платёж
  
  // Размещаем заказ
  await page.click('#place_order');
  
  // Проверяем успешное оформление
  await expect(page.locator('.woocommerce-order-received')).toBeVisible();
  await expect(page.locator('.order-number')).toContainText(/\d+/);
});

У меня был клиент с WooCommerce, где после обновления плагина сломалась интеграция с платёжной системой. Автотесты помогли поймать это за 5 минут вместо нескольких дней жалоб клиентов.

Мониторинг и алертинг

Автотесты — это не только про разработку. Их можно использовать для постоянного мониторинга работоспособности сайта. Особенно полезно для критичных бизнес-процессов.

Регулярные проверки

Я настраиваю автотесты на запуск каждые 30 минут через cron. Если что-то сломалось — сразу получаю уведомление в Slack.

# Cron для регулярного запуска тестов
*/30 * * * * cd /var/www/site && npm run test:critical > /dev/null 2>&1 || echo "Tests failed" | mail -s "Site tests failed" admin@example.com

Для этого создаю отдельный набор "smoke tests" — быстрые тесты критичного функционала:

// tests/smoke/critical.spec.js
import { test, expect } from '@playwright/test';

test.describe('Критичные проверки', () => {
  test('главная страница загружается', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('h1')).toBeVisible({ timeout: 5000 });
  });
  
  test('форма обратной связи работает', async ({ page }) => {
    await page.goto('/contacts/');
    await page.fill('[name="name"]', 'Test');
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="message"]', 'Test message');
    await page.click('[type="submit"]');
    
    await expect(page.locator('.success-message')).toBeVisible();
  });
  
  test('каталог товаров отображается', async ({ page }) => {
    await page.goto('/catalog/');
    await expect(page.locator('.product-item')).toHaveCount({ min: 1 });
  });
});

Интеграция с мониторингом

Автотесты можно интегрировать с системами мониторинга типа Zabbix, Prometheus или DataDog. Я отправляю метрики успешности тестов:

// utils/monitoring.js
export async function sendMetrics(testResults) {
  const metrics = {
    tests_total: testResults.total,
    tests_passed: testResults.passed,
    tests_failed: testResults.failed,
    response_time: testResults.avgResponseTime,
    timestamp: Date.now()
  };
  
  // Отправляем в InfluxDB или другую TSDB
  await fetch('http://monitoring.example.com/api/metrics', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(metrics)
  });
}

Уведомления в Slack

Когда тесты падают, важно быстро об этом узнать. Я настраиваю уведомления в Slack:

// utils/notifications.js
export async function notifySlack(testResults) {
  if (testResults.failed > 0) {
    const message = {
      text: `🚨 Тесты упали на ${process.env.SITE_URL}`,
      attachments: [{
        color: 'danger',
        fields: [
          { title: 'Упавшие тесты', value: testResults.failed, short: true },
          { title: 'Всего тестов', value: testResults.total, short: true },
          { title: 'Время', value: new Date().toLocaleString(), short: true }
        ]
      }]
    };
    
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(message)
    });
  }
}

У одного клиента такой мониторинг помог поймать проблему с платёжным шлюзом в 2 часа ночи. Без автотестов узнали бы только утром по жалобам покупателей.

💡
Совет: Не делайте мониторинговые тесты слишком чувствительными. Лучше пропустить редкий глюк, чем получать ложные срабатывания каждую ночь.

Типичные ошибки и подводные камни

За годы работы с автотестами я наступил на все возможные грабли. Поделюсь самыми болезненными, чтобы вы их избежали.

Хрупкие селекторы

Самая частая проблема — тесты ломаются из-за изменений в вёрстке. Вчера кнопка была `.btn-primary`, сегодня стала `.button-submit` — и все тесты упали.

Плохо:

await page.click('.btn.btn-primary.btn-lg'); // сломается при изменении CSS
await page.click('div > ul > li:nth-child(3) > a'); // сломается при изменении структуры

Хорошо:

await page.click('[data-testid="submit-button"]'); // стабильно
await page.click('text=Отправить заказ'); // понятно и стабильно

Я всегда прошу верстальщиков добавлять data-testid для важных элементов. Это небольшие усилия, которые сэкономят кучу времени.

Зависимость от внешних сервисов

Тесты не должны зависеть от внешних API. Платёжные системы, SMS-сервисы, почтовые провайдеры — всё это может быть недоступно во время тестирования.

Я использую моки для внешних сервисов:

// utils/mocks.js
export async function mockPaymentGateway(page) {
  await page.route('**/api/payment/**', route => {
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        status: 'success',
        transaction_id: 'mock_12345',
        payment_url: '/payment/success'
      })
    });
  });
}

Слишком медленные тесты

Если тесты выполняются больше 10 минут, их никто не будет запускать. Я оптимизирую производительность тестов:

  • Запуск тестов параллельно
  • Переиспользование браузерных сессий
  • Моки вместо реальных HTTP-запросов
  • Отключение изображений и CSS в тестах
// playwright.config.js - оптимизация скорости
use: {
  // Отключаем загрузку изображений
  launchOptions: {
    args: ['--disable-images']
  },
  // Блокируем ненужные ресурсы
  extraHTTPHeaders: {
    'Accept-Language': 'en-US'
  }
}

Недетерминированные тесты

Тесты должны работать одинаково при каждом запуске. Но иногда они падают случайно из-за race conditions, асинхронности, случайных данных.

Типичные проблемы:

  • Использование текущего времени в тестах
  • Зависимость от порядка выполнения
  • Недостаточные таймауты
  • Конкурентное использование одних данных

Решения:

// Плохо - зависит от времени
test('создание события на завтра', async ({ page }) => {
  const tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  await page.fill('[name="date"]', tomorrow.toISOString().split('T')[0]);
});

// Хорошо - фиксированная дата
test('создание события', async ({ page }) => {
  await page.fill('[name="date"]', '2026-12-31');
});

// Плохо - race condition
test('проверка счётчика', async ({ page }) => {
  await page.click('.increment-btn');
  expect(await page.textContent('.counter')).toBe('1');
});

// Хорошо - ждём изменения
test('проверка счётчика', async ({ page }) => {
  await page.click('.increment-btn');
  await expect(page.locator('.counter')).toHaveText('1');
});

Избыточное тестирование

Не нужно тестировать всё подряд. 80% багов можно поймать 20% тестов. Я фокусируюсь на критичном функционале:

  • Регистрация и авторизация
  • Оформление заказов
  • Отправка форм
  • Интеграции с внешними сервисами

А вот тестировать цвет кнопок или отступы — пустая трата времени.

Помните: исправление ошибок на сайте обходится дороже, чем их предотвращение с помощью тестов. Но писать тесты тоже нужно с умом.

⚠️
Главное правило: Тесты должны быть простыми, быстрыми и надёжными. Сложный тест, который часто ломается — хуже отсутствия тестов.

Автоматическое тестирование — это инвестиция в будущее проекта. На начальных этапах кажется, что проще протестировать вручную. Но когда проект растёт, автотесты становятся критично важными для поддержания качества и скорости разработки.

Начните с малого — автоматизируйте тестирование самых важных функций. Постепенно расширяйте покрытие. И помните: лучше 10 стабильных тестов, чем 100 нестабильных.

Хотите повысить качество своего сайта?

Внедрим систему автоматического тестирования, которая выявит ошибки до того, как их увидят пользователи.

П
Павел
Веб-разработчик · 10+ лет опыта · Bitrix, WordPress, Laravel

Читайте также