Параметризованные тесты

Параметризованные тесты позволяют запускать один и тот же тестовый сценарий с различными наборами данных, что повышает покрытие тестами и уменьшает дублирование кода. В WebdriverIO этот подход реализуется через комбинацию JavaScript, Mocha/Jasmine и встроенных возможностей тест-раннера.


Основная идея параметризации

Параметризация заключается в создании структуры данных (массивов или объектов), содержащих наборы тестовых данных, и использовании этих данных внутри цикла или функции, генерирующей тесты. Такой подход особенно эффективен при проверке форм, калькуляторов, фильтров, авторизации, и любых сценариев, где один и тот же процесс должен проверяться с разными значениями.


Пример с Mocha

const testData = [
    { username: 'user1', password: 'pass1', expected: true },
    { username: 'user2', password: 'wrong', expected: false },
    { username: '', password: 'nopass', expected: false }
];

describe('Проверка авторизации', () => {
    testData.forEach(({ username, password, expected }) => {
        it(`Авторизация с логином: ${username} и паролем: ${password}`, async () => {
            await browser.url('https://example.com/login');
            const inputUsername = await $('#username');
            const inputPassword = await $('#password');
            const loginButton = await $('#login');

            await inputUsername.setValue(username);
            await inputPassword.setValue(password);
            await loginButton.click();

            const loginSuccess = await $('#welcome').isDisplayed();
            expect(loginSuccess).toBe(expected);
        });
    });
});

В этом примере один тестовый сценарий повторяется для каждого набора данных. Использование forEach или map позволяет создавать отдельные it блоки динамически, сохраняя читаемость отчётов тест-раннера.


Использование it.each в Jasmine

Jasmine и Jest поддерживают более декларативный способ параметризации через функции вроде it.each:

const credentials = [
    ['user1', 'pass1', true],
    ['user2', 'wrong', false],
    ['', 'nopass', false]
];

describe('Авторизация с разными данными', () => {
    credentials.forEach(([username, password, expected]) => {
        it(`логин: ${username}, пароль: ${password}`, async () => {
            await browser.url('/login');
            await $('#username').setValue(username);
            await $('#password').setValue(password);
            await $('#login').click();

            const result = await $('#welcome').isDisplayed();
            expect(result).toBe(expected);
        });
    });
});

Такой подход обеспечивает компактное определение тестов без необходимости создавать множество одинаковых it блоков вручную.


Параметры через JSON или внешние файлы

Для сложных тестов с большим количеством данных удобно хранить тестовые данные отдельно:

[
    { "username": "user1", "password": "pass1", "expected": true },
    { "username": "user2", "password": "wrong", "expected": false }
]

И использовать их в тестах:

import testData from './testData.json';

describe('Авторизация из JSON', () => {
    testData.forEach(({ username, password, expected }) => {
        it(`логин: ${username}, пароль: ${password}`, async () => {
            await browser.url('/login');
            await $('#username').setValue(username);
            await $('#password').setValue(password);
            await $('#login').click();

            const result = await $('#welcome').isDisplayed();
            expect(result).toBe(expected);
        });
    });
});

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


Динамическая генерация тестов с describe.each

Для ещё более структурированного подхода можно создавать группы параметризированных тестов:

const groups = [
    {
        feature: 'Авторизация',
        tests: [
            { username: 'user1', password: 'pass1', expected: true },
            { username: 'user2', password: 'wrong', expected: false }
        ]
    },
    {
        feature: 'Регистрация',
        tests: [
            { username: 'newuser', password: 'newpass', expected: true },
            { username: '', password: '', expected: false }
        ]
    }
];

groups.forEach(({ feature, tests }) => {
    describe(feature, () => {
        tests.forEach(({ username, password, expected }) => {
            it(`логин: ${username}, пароль: ${password}`, async () => {
                await browser.url(`/${feature.toLowerCase()}`);
                await $('#username').setValue(username);
                await $('#password').setValue(password);
                await $('#submit').click();

                const result = await $('#status').isDisplayed();
                expect(result).toBe(expected);
            });
        });
    });
});

Такой подход позволяет поддерживать модульность и читаемость тестов даже при большом объёме данных.


Использование TypeScript и типизации данных

При использовании TypeScript можно добавить строгую типизацию:

interface AuthTestData {
    username: string;
    password: string;
    expected: boolean;
}

const testData: AuthTestData[] = [
    { username: 'user1', password: 'pass1', expected: true },
    { username: 'user2', password: 'wrong', expected: false }
];

describe('Авторизация (TypeScript)', () => {
    testData.forEach(({ username, password, expected }) => {
        it(`логин: ${username}, пароль: ${password}`, async () => {
            await browser.url('/login');
            await $('#username').setValue(username);
            await $('#password').setValue(password);
            await $('#login').click();

            const result = await $('#welcome').isDisplayed();
            expect(result).toBe(expected);
        });
    });
});

Типизация повышает надёжность тестов и снижает риск ошибок при расширении тестовых данных.


Особенности и лучшие практики

  • Разделение данных и логики теста: данные лучше хранить отдельно от сценариев для облегчения поддержки.
  • Ясные имена тестов: при генерации тестов динамически следует включать данные в название, чтобы отчёты были информативными.
  • Избегать сложных циклов внутри it: лучше создавать отдельные it для каждого набора данных, чтобы каждый тест запускался независимо.
  • Совместимость с отчетами: параметризация через отдельные it блоки сохраняет корректное отображение статуса каждого кейса в Allure, HTML- или JSON-отчетах.

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