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

Пользовательские Команды

Если вы хотите расширить экземпляр browser своим собственным набором команд, метод браузера addCommand поможет вам в этом. Вы можете написать свою команду асинхронно, как и в ваших спецификациях.

Параметры​

Имя команды​

Имя, которое определяет команду и будет прикреплено к области видимости браузера или элемента.

Тип: String

Пользовательская функция​

Функция, которая выполняется при вызове команды. Область видимости this - это либо WebdriverIO.Browser, либо WebdriverIO.Element, в зависимости от того, прикрепляется ли команда к области видимости браузера или элемента.

Тип: Function

Опции​

Объект с опциями конфигурации, изменяющими поведение пользовательской команды

Целевая область​

Флаг для определения, прикреплять ли команду к области видимости браузера или элемента. Если установлено значение true, команда будет командой элемента.

Имя опции: attachToElement Тип: Boolean
По умолчанию: false

Отключение implicitWait​

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

Имя опции: disableElementImplicitWait Тип: Boolean
По умолчанию: false

Примеры​

Этот пример показывает, как добавить новую команду, которая возвращает текущий URL и заголовок как один результат. Область видимости (this) - это объект WebdriverIO.Browser.

browser.addCommand('getUrlAndTitle', async function (customVar) {
// `this` относится к области видимости `browser`
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})

Кроме того, вы можете расширить экземпляр элемента своим собственным набором команд, передав true в качестве последнего аргумента. Область видимости (this) в этом случае - объект WebdriverIO.Element.

browser.addCommand("waitAndClick", async function () {
// `this` - это возвращаемое значение $(selector)
await this.waitForDisplayed()
await this.click()
}, { attachToElement: true })

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

browser.addCommand("waitAndClick", async function () {
// `this` - это возвращаемое значение $(selector)
await this.waitForExists()
await this.click()
}, { attachToElement: true, disableElementImplicitWait: true })

Пользовательские команды дают вам возможность объединить определенную последовательность часто используемых команд в один вызов. Вы можете определить пользовательские команды в любой момент вашего набора тестов; просто убедитесь, что команда определена до ее первого использования. (Хук before в вашем wdio.conf.js - хорошее место для их создания.)

После определения вы можете использовать их следующим образом:

it('should use my custom command', async () => {
await browser.url('http://www.github.com')
const result = await browser.getUrlAndTitle('foobar')

assert.strictEqual(result.url, 'https://github.com/')
assert.strictEqual(result.title, 'GitHub · Where software is built')
assert.strictEqual(result.customVar, 'foobar')
})

Примечание: Если вы регистрируете пользовательскую команду в области видимости browser, команда не будет доступна для элементов. Аналогично, если вы регистрируете команду в области видимости элемента, она не будет доступна в области видимости browser:

browser.addCommand("myCustomBrowserCommand", () => { return 1 })
const elem = await $('body')
console.log(typeof browser.myCustomBrowserCommand) // выводит "function"
console.log(typeof elem.myCustomBrowserCommand()) // выводит "undefined"

browser.addCommand("myCustomElementCommand", () => { return 1 }, { attachToElement: true })
const elem2 = await $('body')
console.log(typeof browser.myCustomElementCommand) // выводит "undefined"
console.log(await elem2.myCustomElementCommand('foobar')) // выводит "1"

const elem3 = await $('body')
elem3.addCommand("myCustomElementCommand2", () => { return 2 })
console.log(typeof browser.myCustomElementCommand2) // выводит "undefined"
console.log(await elem3.myCustomElementCommand2('foobar')) // выводит "2"

Примечание: Если вам нужно цепочкой связывать пользовательскую команду, команда должна заканчиваться символом $,

browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, { attachToElement: true })
await browser.user$('foo').user$('bar').click()

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

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

Multiremote​

addCommand работает аналогично для multiremote, за исключением того, что новая команда будет распространяться на дочерние экземпляры. Вы должны быть осторожны при использовании объекта this, так как multiremote browser и его дочерние экземпляры имеют разные this.

Этот пример показывает, как добавить новую команду для multiremote.

import { multiRemoteBrowser } from '@wdio/globals'

multiRemoteBrowser.addCommand('getUrlAndTitle', async function (this: WebdriverIO.MultiRemoteBrowser, customVar: any) {
// `this` относится к:
// - MultiRemoteBrowser области для браузера
// - Browser области для экземпляров
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})

multiRemoteBrowser.getUrlAndTitle()
/*
{
url: [ 'https://webdriver.io/', 'https://webdriver.io/' ],
title: [
'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO',
'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO'
],
customVar: undefined
}
*/

multiRemoteBrowser.getInstance('browserA').getUrlAndTitle()
/*
{
url: 'https://webdriver.io/',
title: 'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO',
customVar: undefined
}
*/

Расширение определений типов​

С TypeScript легко расширять интерфейсы WebdriverIO. Добавьте типы к вашим пользовательским командам следующим образом:

  1. Создайте файл определения типов (например, ./src/types/wdio.d.ts)

  2. a. Если вы используете файл определения типов в стиле модуля (используя import/export и declare global WebdriverIO в файле определения типов), убедитесь, что путь к файлу включен в свойство include в tsconfig.json.

    b. Если вы используете файлы определения типов в окружающем стиле (без import/export в файлах определения типов и declare namespace WebdriverIO для пользовательских команд), убедитесь, что tsconfig.json не содержит раздел include, так как это приведет к тому, что все файлы определения типов, не перечисленные в разделе include, не будут распознаны TypeScript.

tsconfig.json
{
"compilerOptions": { ... },
"include": [
"./test/**/*.ts",
"./src/types/**/*.ts"
]
}
  1. Добавьте определения для ваших команд в соответствии с режимом выполнения.
declare global {
namespace WebdriverIO {
interface Browser {
browserCustomCommand: (arg: any) => Promise<void>
}

interface MultiRemoteBrowser {
browserCustomCommand: (arg: any) => Promise<void>
}

interface Element {
elementCustomCommand: (arg: any) => Promise<number>
}
}
}

Интеграция сторонних библиотек​

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

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

browser.addCommand('makeRequest', async (url) => {
const response = await fetch(url)
return await response.json()
})

Затем просто используйте это в ваших тестовых спецификациях WDIO:

it('execute external library in a sync way', async () => {
await browser.url('...')
const body = await browser.makeRequest('http://...')
console.log(body) // возвращает тело ответа
})

Примечание: Результат вашей пользовательской команды - это результат промиса, который вы возвращаете.

Переопределение команд​

Вы также можете переопределить встроенные команды с помощью overwriteCommand.

Не рекомендуется делать это, так как это может привести к непредсказуемому поведению фреймворка!

Общий подход аналогичен addCommand, единственное отличие в том, что первым аргументом в функции команды является оригинальная функция, которую вы собираетесь переопределить. Пожалуйста, посмотрите примеры ниже.

Переопределение команд браузера​

/**
* Выводит миллисекунды перед паузой и возвращает их значение.
*
* @param pause - имя команды, которую нужно переопределить
* @param this функции - оригинальный экземпляр браузера, на котором была вызвана функция
* @param originalPauseFunction функции - оригинальная функция паузы
* @param ms функции - фактически переданные параметры
*/
browser.overwriteCommand('pause', async function (this, originalPauseFunction, ms) {
console.log(`sleeping for ${ms}`)
await originalPauseFunction(ms)
return ms
})

// затем используйте как раньше
console.log(`was sleeping for ${await browser.pause(1000)}`)

Переопределение команд элемента​

Переопределение команд на уровне элемента почти такое же. Просто передайте true в качестве третьего аргумента для overwriteCommand:

/**
* Попытка прокрутки к элементу, если он не кликабелен.
* Передайте { force: true }, чтобы кликнуть с помощью JS, даже если элемент не виден или не кликабелен.
* Показывает, что тип аргумента оригинальной функции может быть сохранен с помощью `options?: ClickOptions`
*
* @param this функции - элемент, на котором была вызвана оригинальная функция
* @param originalClickFunction функции - оригинальная функция клика
* @param options функции - фактически переданные параметры
*/
browser.overwriteCommand(
'click',
async function (this, originalClickFunction, options?: ClickOptions & { force?: boolean }) {
const { force, ...restOptions } = options || {}
if (!force) {
try {
// попытка клика
await originalClickFunction(options)
return
} catch (err) {
if ((err as Error).message.includes('not clickable at point')) {
console.warn('WARN: Element', this.selector, 'is not clickable.', 'Scrolling to it before clicking again.')

// прокрутка к элементу и повторный клик
await this.scrollIntoView()
return originalClickFunction(options)
}
throw err
}
}

// клик с помощью js
console.warn('WARN: Using force click for', this.selector)
await browser.execute((el) => {
el.click()
}, this)
},
{ attachToElement: true }, // Не забудьте прикрепить к элементу
)

// затем используйте как раньше
const elem = await $('body')
await elem.click()

// или передайте параметры
await elem.click({ force: true })

Добавление дополнительных команд WebDriver​

Если вы используете протокол WebDriver и запускаете тесты на платформе, которая поддерживает дополнительные команды, не определенные в определениях протоколов в @wdio/protocols, вы можете вручную добавить их через интерфейс addCommand. Пакет webdriver предлагает обертку команд, которая позволяет регистрировать эти новые конечные точки так же, как и другие команды, обеспечивая те же проверки параметров и обработку ошибок. Чтобы зарегистрировать эту новую конечную точку, импортируйте обертку команды и зарегистрируйте новую команду с ее помощью следующим образом:

import { command } from 'webdriver'

browser.addCommand('myNewCommand', command('POST', '/session/:sessionId/foobar/:someId', {
command: 'myNewCommand',
description: 'a new WebDriver command',
ref: 'https://vendor.com/commands/#myNewCommand',
variables: [{
name: 'someId',
description: 'some id to something'
}],
parameters: [{
name: 'foo',
type: 'string',
description: 'a valid parameter',
required: true
}]
}))

Вызов этой команды с неверными параметрами приводит к той же обработке ошибок, что и предопределенные команды протокола, например:

// вызов команды без обязательного параметра url и полезной нагрузки
await browser.myNewCommand()

/**
* результат следующей ошибки:
* Error: Wrong parameters applied for myNewCommand
* Usage: myNewCommand(someId, foo)
*
* Property Description:
* "someId" (string): some id to something
* "foo" (string): a valid parameter
*
* For more info see https://my-api.com
* at Browser.protocolCommand (...)
* ...
*/

Правильный вызов команды, например, browser.myNewCommand('foo', 'bar'), корректно делает запрос WebDriver к, например, http://localhost:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo с полезной нагрузкой типа { foo: 'bar' }.

примечание

Параметр URL :sessionId будет автоматически заменен идентификатором сессии сессии WebDriver. Другие параметры URL могут быть применены, но должны быть определены в variables.

Примеры того, как могут быть определены команды протокола, смотрите в пакете @wdio/protocols.

Welcome! How can I help?

WebdriverIO AI Copilot