Основы современного JavaScript: синтаксис ES6+, работа с массивами и асинхронность
Основы современного JavaScript: синтаксис ES6+, работа с массивами и асинхронность
Добро пожаловать в курс JavaScript и TypeScript для автоматизации тестирования. Это первая статья, и мы начнем с фундамента, на котором строится вся современная веб-разработка и автоматизация.
Почему именно JavaScript (и его «старший брат» TypeScript)? Потому что это язык веба. Если вы пишете автотесты для браузера (UI) или тестируете API Node.js сервисов, знание внутренней кухни JS позволит вам писать стабильные, быстрые и поддерживаемые тесты. Современные инструменты, такие как Playwright, Cypress или WebdriverIO, требуют уверенного владения синтаксисом ES6+.
В этой статье мы разберем ключевые нововведения стандарта ECMAScript 2015 (ES6) и более поздних версий, научимся эффективно работать с данными в массивах и, самое главное, разберем асинхронность — тему, без которой невозможно написать ни один стабильный автотест.
Переменные: прощай, var
В старом JavaScript для объявления переменных использовалось ключевое слово var. У него было много проблем, связанных с областью видимости, что часто приводило к трудноуловимым ошибкам. В современном стандарте мы используем let и const.
const (Константа)
Это ваш выбор по умолчанию. Если вы создаете переменную и не планируете переприсваивать ей значение (например, селектор кнопки или базовый URL), используйте const.
const baseUrl = 'https://example.com';
const timeout = 5000;
// baseUrl = 'https://google.com'; // Ошибка! Нельзя переприсвоить константу
Важно понимать: const запрещает переприсваивание самой переменной, но если в переменной лежит объект или массив, их содержимое менять можно.
const user = { name: 'Alice' };
user.name = 'Bob'; // Это допустимо
let (Переменная)
Используйте let только тогда, когда значение переменной действительно будет меняться в процессе выполнения кода. Например, счетчик в цикле или переменная, хранящая текущий статус теста.
let retryCount = 0;
retryCount = 1; // Допустимо
Стрелочные функции (Arrow Functions)
Стрелочные функции сделали код лаконичнее и решили старую проблему с потерей контекста this (хотя в автотестах проблема this встречается реже, краткость очень важна).
Классическая функция:
function sum(a, b) {
return a + b;
}
Стрелочная функция:
const sum = (a, b) => {
return a + b;
};
Если тело функции состоит из одной строки, можно убрать фигурные скобки и слово return — возврат значения произойдет автоматически (неявный возврат):
const sum = (a, b) => a + b;
const double = n => n * 2; // Если аргумент один, скобки вокруг него тоже можно опустить
В тестах это выглядит очень элегантно, например, при проверке условий ожиданий.
Шаблонные строки (Template Literals)
Раньше для склеивания строк мы использовали оператор +. Это было неудобно, особенно при формировании сложных локаторов или сообщений об ошибках.
Теперь мы используем обратные кавычки (backticks) ` и конструкцию ${} для вставки переменных.
const elementId = 42;
const selector = `#user-list li[data-id="${elementId}"]`;
console.log(`Поиск элемента с селектором: ${selector}`);
Это критически важно для создания динамических локаторов в Page Object паттерне.
Деструктуризация и Spread-оператор
Эти инструменты позволяют легко извлекать данные из объектов и массивов, а также копировать их.
Деструктуризация объектов
Представьте, что API вернул вам большой JSON-объект пользователя, а вам для теста нужны только id и email.
const response = {
id: 101,
name: 'Test User',
email: 'test@example.com',
role: 'admin'
};
// Старый способ
// const id = response.id;
// const email = response.email;
// Новый способ
const { id, email } = response;
console.log(id, email); // 101, 'test@example.com'
Spread-оператор (...)
Позволяет «развернуть» объект или массив. Часто используется для создания тестовых данных, когда нужно взять базовый объект и переопределить в нем одно поле.
const defaultUser = { name: 'User', password: '123', role: 'user' };
// Создаем админа на основе обычного юзера
const adminUser = { ...defaultUser, role: 'admin' };
console.log(adminUser);
// { name: 'User', password: '123', role: 'admin' }
Работа с массивами: методы высшего порядка
В автоматизации мы постоянно работаем со списками: список товаров в корзине, список результатов поиска, список ошибок в форме. Циклы for используются все реже. Вместо них применяются методы массивов.
map()
Используется для трансформации данных. Он берет массив, применяет функцию к каждому элементу и возвращает новый массив той же длины.
Пример: Получить массив текстов из массива веб-элементов.
const prices = [100, 200, 300];
const pricesWithTax = prices.map(price => price * 1.2);
// [120, 240, 360]
filter()
Используется для фильтрации данных. Возвращает новый массив, содержащий только те элементы, которые прошли проверку (вернули true).
Пример: Найти все активные кнопки на странице.
const elements = [
{ id: 1, visible: true },
{ id: 2, visible: false },
{ id: 3, visible: true }
];
const visibleElements = elements.filter(el => el.visible);
// [{ id: 1, visible: true }, { id: 3, visible: true }]
find()
Похож на filter, но возвращает первый найденный элемент, соответствующий условию, или undefined, если ничего не найдено.
Пример: Найти конкретного пользователя в списке.
const users = [{ name: 'Alice' }, { name: 'Bob' }];
const bob = users.find(user => user.name === 'Bob');
// { name: 'Bob' }
Асинхронность: Promise и Async/Await
Это самая сложная, но самая важная тема для автоматизатора. JavaScript — однопоточный язык. Это значит, что он может делать только одну вещь одновременно. Однако операции в браузере (загрузка страницы, ожидание элемента, запрос к API) занимают время.
Если бы JS просто ждал завершения этих операций, интерфейс бы «зависал». Чтобы этого избежать, используется асинхронность.
Промисы (Promises)
Promise (обещание) — это объект, который представляет собой результат асинхронной операции. Он может находиться в трех состояниях:
- Pending (ожидание): операция еще не завершена.
- Fulfilled (выполнено): операция завершена успешно.
- Rejected (отклонено): произошла ошибка.
Раньше мы работали с промисами через методы .then() и .catch(), что приводило к вложенным конструкциям («Callback Hell»).
Async / Await
В современном JS (ES8) появился синтаксис async/await, который позволяет писать асинхронный код так, как будто он синхронный. Это «золотой стандарт» для автотестов.
- async перед функцией говорит, что эта функция всегда возвращает Промис.
- await можно использовать только внутри
asyncфункций. Он заставляет интерпретатор «подождать», пока Промис не выполнится, и только потом продолжить выполнение кода.
Пример типичного теста на псевдокоде:
// Объявляем асинхронную функцию теста
async function testLogin() {
// Ждем, пока браузер откроет страницу. Код дальше не пойдет, пока это не случится.
await browser.open('/login');
// Ждем, пока найдем элемент и введем текст
await browser.type('#username', 'admin');
await browser.type('#password', '12345');
// Ждем клика
await browser.click('#submit-btn');
// Ждем появления элемента на новой странице
const welcomeMessage = await browser.getText('#welcome');
if (welcomeMessage === 'Hello, admin') {
console.log('Test Passed');
}
}
Без await код попытался бы кликнуть по кнопке еще до того, как браузер успел бы загрузить страницу, что привело бы к падению теста.
Обработка ошибок: try...catch
При использовании async/await ошибки обрабатываются с помощью классического блока try...catch.
async function getData() {
try {
const response = await api.get('/users');
console.log(response.data);
} catch (error) {
console.error('Не удалось получить данные:', error);
}
}
Заключение
Мы рассмотрели основные кирпичики современного JavaScript:
letиconstдля управления данными.- Стрелочные функции для краткости.
- Шаблонные строки для динамического текста.
- Методы массивов
map,filter,findдля работы с коллекциями. async/awaitдля управления потоком выполнения в тестах.
Эти знания — абсолютный минимум для старта. В следующей статье мы поговорим о том, как добавить строгую типизацию в этот хаос с помощью TypeScript, и почему это спасет вам сотни часов отладки.