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

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

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

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

Используя селекторы, устойчивые к изменениям в 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, что является стратегией для доступа к shadow 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