Перейти к основному содержанию

Лучшие практики

Это руководство поможет вам узнать наши лучшие практики, которые помогут писать производительные и надежные тесты.

Используйте устойчивые селекторы

Используя селекторы, устойчивые к изменениям в DOM, у вас будет меньше или вообще не будет падающих тестов, когда, например, класс будет удален из элемента.

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

// 👎
await $('.button')

Все эти селекторы должны возвращать один элемент.

// 👍
await $('aria/Submit')
await $('[test-id="submit-button"]')
await $('#submit-button')

Примечание: Чтобы узнать о всех возможных селекторах, которые поддерживает WebdriverIO, посмотрите нашу страницу Selectors.

Ограничьте количество запросов элементов

Каждый раз, когда вы используете команду $ или $$ (это включает их цепочки), WebdriverIO пытается найти элемент в DOM. Эти запросы дорогостоящие, поэтому вы должны стараться ограничивать их как можно больше.

Запрашивает три элемента.

// 👎
await $('table').$('tr').$('td')

Запрашивает только один элемент.

// 👍
await $('table tr td')

Единственный случай, когда вы должны использовать цепочки - это когда вы хотите комбинировать различные стратегии селекторов. В примере мы используем Deep Selectors, что является стратегией проникновения в теневой DOM элемента.

// 👍
await $('custom-datepicker').$('#calendar').$('aria/Select')

Предпочитайте поиск одного элемента вместо выбора из списка

Это не всегда возможно, но используя CSS псевдоклассы, такие как :nth-child, вы можете сопоставлять элементы на основе индексов элементов в дочернем списке их родителей.

Запрашивает все строки таблицы.

// 👎
await $$('table tr')[15]

Запрашивает одну строку таблицы.

// 👍
await $('table tr:nth-child(15)')

Используйте встроенные утверждения

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

// 👎
expect(await button.isDisplayed()).toBe(true)

Используя встроенные утверждения, WebdriverIO будет автоматически ждать, пока фактический результат не совпадет с ожидаемым результатом, что приведет к устойчивым тестам. Это достигается за счет автоматического повторения утверждения, пока оно не пройдет или не истечет время ожидания.

// 👍
await expect(button).toBeDisplayed()

Ленивая загрузка и цепочки обещаний

WebdriverIO имеет несколько хитростей в рукаве, когда дело доходит до написания чистого кода, так как он может лениво загружать элемент, что позволяет вам создавать цепочки обещаний и уменьшать количество await. Это также позволяет передавать элемент как ChainablePromiseElement вместо Element и для более удобного использования с объектами страниц.

Так когда же вам нужно использовать await? Вы всегда должны использовать await за исключением команд $ и $$.

// 👎
const div = await $('div')
const button = await div.$('button')
await button.click()
// или
await (await (await $('div')).$('button')).click()
// 👍
const button = $('div').$('button')
await button.click()
// или
await $('div').$('button').click()

Не злоупотребляйте командами и утверждениями

Когда вы используете expect.toBeDisplayed, вы неявно также ждете, пока элемент будет существовать. Нет необходимости использовать команды waitForXXX, когда у вас уже есть утверждение, выполняющее то же самое.

// 👎
await button.waitForExist()
await expect(button).toBeDisplayed()

// 👎
await button.waitForDisplayed()
await expect(button).toBeDisplayed()

// 👍
await expect(button).toBeDisplayed()

Нет необходимости ждать, пока элемент будет существовать или отображаться при взаимодействии или при утверждении чего-то вроде его текста, если элемент не может быть явно невидимым (например, opacity: 0) или явно отключенным (например, атрибут disabled), в этом случае ожидание отображения элемента имеет смысл.

// 👎
await expect(button).toBeExisting()
await expect(button).toHaveText('Submit')

// 👎
await expect(button).toBeDisplayed()
await expect(button).toHaveText('Submit')

// 👎
await expect(button).toBeDisplayed()
await button.click()
// 👍
await button.click()

// 👍
await expect(button).toHaveText('Submit')

Динамические тесты

Используйте переменные окружения для хранения динамических тестовых данных, например, секретных учетных данных, в вашей среде, а не жестко кодируйте их в тест. Перейдите на страницу Parameterize Tests для получения дополнительной информации по этой теме.

Проверяйте свой код с помощью линтера

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

Не используйте паузы

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

// 👎
await nameInput.setValue('Bob')
await browser.pause(200) // ждем, пока кнопка отправки станет активной
await submitFormButton.click()

// 👍
await nameInput.setValue('Bob')
await submitFormButton.waitForEnabled()
await submitFormButton.click()

Асинхронные циклы

Когда у вас есть асинхронный код, который вы хотите повторить, важно знать, что не все циклы могут это делать. Например, функция forEach массива не позволяет использовать асинхронные обратные вызовы, как можно прочитать на MDN.

Примечание: Вы все еще можете использовать их, когда вам не нужно, чтобы операция была асинхронной, как показано в этом примере console.log(await $$('h1').map((h1) => h1.getText())).

Ниже приведены примеры того, что это значит.

Следующий код не будет работать, так как асинхронные обратные вызовы не поддерживаются.

// 👎
const characters = 'this is some example text that should be put in order'
characters.forEach(async (character) => {
await browser.keys(character)
})

Следующий код будет работать.

// 👍
const characters = 'this is some example text that should be put in order'
for (const character of characters) {
await browser.keys(character)
}

Сохраняйте простоту

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

// 👎 слишком сложно, синхронное утверждение, используйте встроенные утверждения для предотвращения нестабильных тестов
const headerText = ['Products', 'Prices']
const texts = await $$('th').map(e => e.getText());
expect(texts).toBe(headerText)

// 👎 слишком сложно
const headerText = ['Products', 'Prices']
const columns = await $$('th');
await expect(columns).toBeElementsArrayOfSize(2);
for (let i = 0; i < columns.length; i++) {
await expect(columns[i]).toHaveText(headerText[i]);
}

// 👎 находит элементы по их тексту, но не учитывает положение элементов
await expect($('th=Products')).toExist();
await expect($('th=Prices')).toExist();
// 👍 используйте уникальные идентификаторы (часто используется для пользовательских элементов)
await expect($('[data-testid="Products"]')).toHaveText('Products');
// 👍 имена доступности (часто используются для нативных html-элементов)
await expect($('aria/Product Prices')).toHaveText('Prices');

Еще одна вещь, которую мы иногда наблюдаем, - это то, что простые вещи имеют слишком сложное решение.

// 👎
class BadExample {
public async selectOptionByValue(value: string) {
await $('select').click();
await $$('option')
.map(async function (element) {
const hasValue = (await element.getValue()) === value;
if (hasValue) {
await $(element).click();
}
return hasValue;
});
}

public async selectOptionByText(text: string) {
await $('select').click();
await $$('option')
.map(async function (element) {
const hasText = (await element.getText()) === text;
if (hasText) {
await $(element).click();
}
return hasText;
});
}
}
// 👍
class BetterExample {
public async selectOptionByValue(value: string) {
await $('select').click();
await $(`option[value=${value}]`).click();
}

public async selectOptionByText(text: string) {
await $('select').click();
await $(`option=${text}]`).click();
}
}

Выполнение кода параллельно

Если вам не важен порядок выполнения некоторого кода, вы можете использовать Promise.all для ускорения выполнения.

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

// 👎
await name.setValue('Bob')
await email.setValue('bob@webdriver.io')
await age.setValue('50')
await submitFormButton.waitForEnabled()
await submitFormButton.click()

// 👍
await Promise.all([
name.setValue('Bob'),
email.setValue('bob@webdriver.io'),
age.setValue('50'),
])
await submitFormButton.waitForEnabled()
await submitFormButton.click()

После абстрагирования это может выглядеть примерно так, где логика помещена в метод, называемый submitWithDataOf, а данные получаются классом Person.

// 👍
await form.submitData(new Person('bob@webdriver.io'))

Welcome! How can I help?

WebdriverIO AI Copilot