Comandos Personalizados
Se você quiser estender a instância do browser
com seu próprio conjunto de comandos, o método do navegador addCommand
está aqui para você. Você pode escrever seu comando de forma assíncrona, assim como em suas especificações.
Parâmetros
Nome do Comando
Um nome que define o comando e será anexado ao escopo do navegador ou elemento.
Tipo: String
Função Personalizada
Uma função que é executada quando o comando é chamado. O escopo this
é WebdriverIO.Browser
ou WebdriverIO.Element
, dependendo se o comando é anexado ao escopo do navegador ou do elemento.
Tipo: Function
Escopo Alvo
Flag para decidir se o comando deve ser anexado ao escopo do navegador ou do elemento. Se definido como true
, o comando será um comando de elemento.
Tipo: Boolean
Padrão: false
Exemplos
Este exemplo mostra como adicionar um novo comando que retorna a URL atual e o título como um único resultado. O escopo (this
) é um objeto WebdriverIO.Browser
.
browser.addCommand('getUrlAndTitle', async function (customVar) {
// `this` refere-se ao escopo do `browser`
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})
Além disso, você pode estender a instância do elemento com seu próprio conjunto de comandos, passando true
como argumento final. O escopo (this
) neste caso é um objeto WebdriverIO.Element
.
browser.addCommand("waitAndClick", async function () {
// `this` é o valor de retorno de $(selector)
await this.waitForDisplayed()
await this.click()
}, true)
Comandos personalizados dão a oportunidade de agrupar uma sequência específica de comandos que você usa frequentemente como uma única chamada. Você pode definir comandos personalizados em qualquer ponto de sua suíte de testes; apenas certifique-se de que o comando seja definido antes de seu primeiro uso. (O hook before
no seu wdio.conf.js
é um bom lugar para criá-los.)
Uma vez definidos, você pode usá-los da seguinte forma:
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')
})
Nota: Se você registrar um comando personalizado no escopo do browser
, o comando não ficará acessível para elementos. Da mesma forma, se você registrar um comando no escopo do elemento, ele não ficará acessível no escopo do browser
:
browser.addCommand("myCustomBrowserCommand", () => { return 1 })
const elem = await $('body')
console.log(typeof browser.myCustomBrowserCommand) // saída "function"
console.log(typeof elem.myCustomBrowserCommand()) // saída "undefined"
browser.addCommand("myCustomElementCommand", () => { return 1 }, true)
const elem2 = await $('body')
console.log(typeof browser.myCustomElementCommand) // saída "undefined"
console.log(await elem2.myCustomElementCommand('foobar')) // saída "1"
const elem3 = await $('body')
elem3.addCommand("myCustomElementCommand2", () => { return 2 })
console.log(typeof browser.myCustomElementCommand2) // saída "undefined"
console.log(await elem3.myCustomElementCommand2('foobar')) // saída "2"
Nota: Se você precisar encadear um comando personalizado, o comando deve terminar com $
,
browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, true)
await browser.user$('foo').user$('bar').click()
Tenha cuidado para não sobrecarregar o escopo do browser
com muitos comandos personalizados.
Recomendamos definir lógica personalizada em objetos de página, para que estejam vinculados a uma página específica.
Multiremote
addCommand
funciona de maneira semelhante para multiremote, exceto que o novo comando se propagará para as instâncias filhas. Você deve estar atento ao usar o objeto this
já que o browser
multiremote e suas instâncias filhas têm diferentes this
.
Este exemplo mostra como adicionar um novo comando para multiremote.
import { multiremotebrowser } from '@wdio/globals'
multiremotebrowser.addCommand('getUrlAndTitle', async function (this: WebdriverIO.MultiRemoteBrowser, customVar: any) {
// `this` refere-se a:
// - Escopo MultiRemoteBrowser para browser
// - Escopo Browser para instâncias
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
}
*/
Estender Definições de Tipo
Com TypeScript, é fácil estender as interfaces do WebdriverIO. Adicione tipos aos seus comandos personalizados assim:
-
Crie um arquivo de definição de tipo (por exemplo,
./src/types/wdio.d.ts
) -
a. Se estiver usando um arquivo de definição de tipo estilo módulo (usando import/export e
declare global WebdriverIO
no arquivo de definição de tipo), certifique-se de incluir o caminho do arquivo na propriedadeinclude
dotsconfig.json
.b. Se estiver usando arquivos de definição de tipo estilo ambiente (sem import/export nos arquivos de definição de tipo e
declare namespace WebdriverIO
para comandos personalizados), certifique-se de que otsconfig.json
não contenha nenhuma seçãoinclude
, pois isso fará com que todos os arquivos de definição de tipo não listados na seçãoinclude
não sejam reconhecidos pelo typescript.
- Módulos (usando import/export)
- Definições de Tipo Ambiente (sem include no tsconfig)
{
"compilerOptions": { ... },
"include": [
"./test/**/*.ts",
"./src/types/**/*.ts"
]
}
{
"compilerOptions": { ... }
}
- Adicione definições para seus comandos de acordo com seu modo de execução.
- Módulos (usando import/export)
- Definições de Tipo Ambiente
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>
}
}
}
declare namespace WebdriverIO {
interface Browser {
browserCustomCommand: (arg: any) => Promise<void>
}
interface MultiRemoteBrowser {
browserCustomCommand: (arg: any) => Promise<void>
}
interface Element {
elementCustomCommand: (arg: any) => Promise<number>
}
}
Integrar Bibliotecas de Terceiros
Se você usa bibliotecas externas (por exemplo, para fazer chamadas de banco de dados) que suportam promises, uma boa abordagem para integrá-las é envolver certos métodos da API com um comando personalizado.
Ao retornar a promise, o WebdriverIO garante que não continue com o próximo comando até que a promise seja resolvida. Se a promise for rejeitada, o comando lançará um erro.
browser.addCommand('makeRequest', async (url) => {
const response = await fetch(url)
return await response.json()
})
Então, basta usá-lo em suas especificações de teste WDIO:
it('execute external library in a sync way', async () => {
await browser.url('...')
const body = await browser.makeRequest('http://...')
console.log(body) // retorna o corpo da resposta
})
Nota: O resultado do seu comando personalizado é o resultado da promise que você retorna.
Sobrescrevendo Comandos
Você também pode sobrescrever comandos nativos com overwriteCommand
.
Não é recomendado fazer isso, pois pode levar a um comportamento imprevisível do framework!
A abordagem geral é semelhante a addCommand
, a única diferença é que o primeiro argumento na função de comando é a função original que você está prestes a sobrescrever. Por favor, veja alguns exemplos abaixo.
Sobrescrevendo Comandos do Navegador
/**
* imprime milissegundos antes da pausa e retorna seu valor.
*/
// 'pause' - nome do comando a ser sobrescrito
// origPauseFunction - função original de pausa
browser.overwriteCommand('pause', async (origPauseFunction, ms) => {
console.log(`sleeping for ${ms}`)
await origPauseFunction(ms)
return ms
})
// então use-o como antes
console.log(`was sleeping for ${await browser.pause(1000)}`)
Sobrescrevendo Comandos de Elementos
Sobrescrever comandos no nível do elemento é quase o mesmo. Simplesmente passe true
como o terceiro argumento para overwriteCommand
:
/**
* Tenta rolar até o elemento se ele não for clicável.
* Passe { force: true } para clicar com JS mesmo se o elemento não estiver visível ou clicável.
*/
// 'click' - nome do comando a ser sobrescrito
// origClickFunction - função original de clique
browser.overwriteCommand('click', async function (origClickFunction, { force = false } = {}) {
if (!force) {
try {
// tentativa de clique
await origClickFunction()
return null
} catch (err) {
if (err.message.includes('not clickable at point')) {
console.warn('WARN: Element', this.selector, 'is not clickable.',
'Scrolling to it before clicking again.')
// rola até o elemento e clica novamente
await this.scrollIntoView()
return origClickFunction()
}
throw err
}
}
// clicando com js
console.warn('WARN: Using force click for', this.selector)
await browser.execute((el) => {
el.click()
}, this)
}, true) // não esqueça de passar `true` como 3º argumento
// então use-o como antes
const elem = await $('body')
await elem.click()
// ou passe parâmetros
await elem.click({ force: true })
Adicionar Mais Comandos WebDriver
Se você estiver usando o protocolo WebDriver e executando testes em uma plataforma que suporta comandos adicionais não definidos por nenhuma das definições de protocolo em @wdio/protocols
, você pode adicioná-los manualmente através da interface addCommand
. O pacote webdriver
oferece um wrapper de comando que permite registrar esses novos endpoints da mesma forma que outros comandos, fornecendo as mesmas verificações de parâmetros e tratamento de erros. Para registrar este novo endpoint, importe o wrapper de comando e registre um novo comando com ele da seguinte forma:
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
}]
}))
Chamar este comando com parâmetros inválidos resulta no mesmo tratamento de erro que os comandos de protocolo predefinidos, por exemplo:
// chamando o comando sem o parâmetro de url obrigatório e payload
await browser.myNewCommand()
/**
* resulta no seguinte erro:
* 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 (...)
* ...
*/
Chamando o comando corretamente, por exemplo, browser.myNewCommand('foo', 'bar')
, faz corretamente uma requisição WebDriver para, por exemplo, http://localhost:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo
com um payload como { foo: 'bar' }
.
O parâmetro de url :sessionId
será automaticamente substituído pelo id da sessão da sessão WebDriver. Outros parâmetros de url podem ser aplicados, mas precisam ser definidos dentro de variables
.
Veja exemplos de como os comandos de protocolo podem ser definidos no pacote @wdio/protocols
.