Comandos Personalizados
Si deseas extender la instancia browser
con tu propio conjunto de comandos, el método del navegador addCommand
está aquí para ti. Puedes escribir tu comando de forma asíncrona, igual que en tus especificaciones.
Parámetros
Nombre del Comando
Un nombre que define el comando y que se adjuntará al ámbito del navegador o elemento.
Tipo: String
Función Personalizada
Una función que se ejecuta cuando se llama al comando. El ámbito this
es WebdriverIO.Browser
o WebdriverIO.Element
dependiendo de si el comando se adjunta al ámbito del navegador o del elemento.
Tipo: Function
Opciones
Objeto con opciones de configuración que modifican el comportamiento del comando personalizado
Ámbito Objetivo
Bandera para decidir si adjuntar el comando al ámbito del navegador o del elemento. Si se establece en true
, el comando será un comando de elemento.
Nombre de Opción: attachToElement
Tipo: Boolean
Predeterminado: false
Desactivar implicitWait
Bandera para decidir si esperar implícitamente a que el elemento exista antes de llamar al comando personalizado.
Nombre de Opción: disableElementImplicitWait
Tipo: Boolean
Predeterminado: false
Ejemplos
Este ejemplo muestra cómo agregar un nuevo comando que devuelve la URL actual y el título como un solo resultado. El ámbito (this
) es un objeto WebdriverIO.Browser
.
browser.addCommand('getUrlAndTitle', async function (customVar) {
// `this` se refiere al ámbito del `browser`
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})
Además, puedes extender la instancia del elemento con tu propio conjunto de comandos, pasando true
como argumento final. El ámbito (this
) en este caso es un objeto WebdriverIO.Element
.
browser.addCommand("waitAndClick", async function () {
// `this` es el valor de retorno de $(selector)
await this.waitForDisplayed()
await this.click()
}, { attachToElement: true })
Por defecto, los comandos personalizados de elementos esperan a que el elemento exista antes de llamar al comando personalizado. Aunque la mayoría de las veces esto es lo deseado, si no lo es, se puede desactivar con disableImplicitWait
:
browser.addCommand("waitAndClick", async function () {
// `this` es el valor de retorno de $(selector)
await this.waitForExists()
await this.click()
}, { attachToElement: true, disableElementImplicitWait: true })
Los comandos personalizados te dan la oportunidad de agrupar una secuencia específica de comandos que usas con frecuencia en una sola llamada. Puedes definir comandos personalizados en cualquier punto de tu suite de pruebas; solo asegúrate de que el comando esté definido antes de su primer uso. (El hook before
en tu wdio.conf.js
es un buen lugar para crearlos.)
Una vez definidos, puedes usarlos de la siguiente manera:
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: Si registras un comando personalizado en el ámbito browser
, el comando no será accesible para los elementos. Del mismo modo, si registras un comando en el ámbito del elemento, no será accesible en el ámbito browser
:
browser.addCommand("myCustomBrowserCommand", () => { return 1 })
const elem = await $('body')
console.log(typeof browser.myCustomBrowserCommand) // muestra "function"
console.log(typeof elem.myCustomBrowserCommand()) // muestra "undefined"
browser.addCommand("myCustomElementCommand", () => { return 1 }, { attachToElement: true })
const elem2 = await $('body')
console.log(typeof browser.myCustomElementCommand) // muestra "undefined"
console.log(await elem2.myCustomElementCommand('foobar')) // muestra "1"
const elem3 = await $('body')
elem3.addCommand("myCustomElementCommand2", () => { return 2 })
console.log(typeof browser.myCustomElementCommand2) // muestra "undefined"
console.log(await elem3.myCustomElementCommand2('foobar')) // muestra "2"
Nota: Si necesitas encadenar un comando personalizado, el comando debe terminar con $
,
browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, { attachToElement: true })
await browser.user$('foo').user$('bar').click()
Ten cuidado de no sobrecargar el ámbito browser
con demasiados comandos personalizados.
Recomendamos definir lógica personalizada en objetos de página, para que estén vinculados a una página específica.
Multiremote
addCommand
funciona de manera similar para multiremote, excepto que el nuevo comando se propagará a las instancias hijas. Debes tener cuidado al usar el objeto this
ya que el browser
multiremote y sus instancias hijas tienen diferentes this
.
Este ejemplo muestra cómo agregar un nuevo comando para multiremote.
import { multiremotebrowser } from '@wdio/globals'
multiremotebrowser.addCommand('getUrlAndTitle', async function (this: WebdriverIO.MultiRemoteBrowser, customVar: any) {
// `this` se refiere a:
// - ámbito MultiRemoteBrowser para browser
// - ámbito Browser para instancias
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
}
*/
Extender Definiciones de Tipo
Con TypeScript, es fácil extender las interfaces de WebdriverIO. Agrega tipos a tus comandos personalizados así:
-
Crea un archivo de definición de tipos (por ejemplo,
./src/types/wdio.d.ts
) -
a. Si usas un archivo de definición de tipos estilo módulo (usando import/export y
declare global WebdriverIO
en el archivo de definición de tipos), asegúrate de incluir la ruta del archivo en la propiedadinclude
detsconfig.json
.b. Si usas archivos de definición de tipos de estilo ambiente (sin import/export en los archivos de definición de tipos y
declare namespace WebdriverIO
para comandos personalizados), asegúrate de quetsconfig.json
no contenga ninguna seccióninclude
, ya que esto hará que todos los archivos de definición de tipos no listados en la seccióninclude
no sean reconocidos por TypeScript.
- Módulos (usando import/export)
- Definiciones de Tipo Ambiente (sin include en tsconfig)
{
"compilerOptions": { ... },
"include": [
"./test/**/*.ts",
"./src/types/**/*.ts"
]
}
{
"compilerOptions": { ... }
}
- Agrega definiciones para tus comandos según tu modo de ejecución.
- Módulos (usando import/export)
- Definiciones 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 Terceros
Si utilizas bibliotecas externas (por ejemplo, para hacer llamadas a bases de datos) que admiten promesas, un buen enfoque para integrarlas es envolver ciertos métodos de API con un comando personalizado.
Al devolver la promesa, WebdriverIO se asegura de que no continúa con el siguiente comando hasta que la promesa se resuelva. Si la promesa es rechazada, el comando lanzará un error.
browser.addCommand('makeRequest', async (url) => {
const response = await fetch(url)
return await response.json()
})
Luego, simplemente úsalo en tus especificaciones de prueba de WDIO:
it('execute external library in a sync way', async () => {
await browser.url('...')
const body = await browser.makeRequest('http://...')
console.log(body) // devuelve el cuerpo de la respuesta
})
Nota: El resultado de tu comando personalizado es el resultado de la promesa que devuelves.
Sobrescribir Comandos
También puedes sobrescribir comandos nativos con overwriteCommand
.
No se recomienda hacer esto, ¡porque puede conducir a un comportamiento impredecible del framework!
El enfoque general es similar a addCommand
, la única diferencia es que el primer argumento en la función del comando es la función original que vas a sobrescribir. Por favor, mira algunos ejemplos a continuación.
Sobrescribir Comandos del Navegador
/**
* Imprime milisegundos antes de la pausa y devuelve su valor.
*
* @param pause - nombre del comando a sobrescribir
* @param this de func - la instancia original del navegador en la que se llamó a la función
* @param originalPauseFunction de func - la función de pausa original
* @param ms de func - los parámetros reales pasados
*/
browser.overwriteCommand('pause', async function (this, originalPauseFunction, ms) {
console.log(`sleeping for ${ms}`)
await originalPauseFunction(ms)
return ms
})
// luego úsalo como antes
console.log(`was sleeping for ${await browser.pause(1000)}`)
Sobrescribir Comandos de Elementos
Sobrescribir comandos a nivel de elemento es casi lo mismo. Simplemente pasa true
como tercer argumento a overwriteCommand
:
/**
* Intenta desplazarse al elemento si no es clicable.
* Pasa { force: true } para hacer clic con JS incluso si el elemento no es visible o clicable.
* Muestra que se puede mantener el tipo de argumento de la función original con `options?: ClickOptions`
*
* @param this de func - el elemento sobre el que se llamó a la función original
* @param originalClickFunction de func - la función de pausa original
* @param options de func - los parámetros reales pasados
*/
browser.overwriteCommand(
'click',
async function (this, originalClickFunction, options?: ClickOptions & { force?: boolean }) {
const { force, ...restOptions } = options || {}
if (!force) {
try {
// intenta hacer clic
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.')
// desplázate al elemento y haz clic de nuevo
await this.scrollIntoView()
return originalClickFunction(options)
}
throw err
}
}
// haciendo clic con js
console.warn('WARN: Using force click for', this.selector)
await browser.execute((el) => {
el.click()
}, this)
},
{ attachToElement: true }, // No olvides adjuntarlo al elemento
)
// luego úsalo como antes
const elem = await $('body')
await elem.click()
// o pasa parámetros
await elem.click({ force: true })
Agregar Más Comandos de WebDriver
Si estás usando el protocolo WebDriver y ejecutas pruebas en una plataforma que admite comandos adicionales no definidos por ninguna de las definiciones de protocolo en @wdio/protocols
, puedes agregarlos manualmente a través de la interfaz addCommand
. El paquete webdriver
ofrece un envoltorio de comando que permite registrar estos nuevos endpoints de la misma manera que otros comandos, proporcionando las mismas verificaciones de parámetros y manejo de errores. Para registrar este nuevo endpoint, importa el envoltorio de comando y registra un nuevo comando con él de la siguiente manera:
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
}]
}))
Llamar a este comando con parámetros inválidos resulta en el mismo manejo de errores que los comandos de protocolo predefinidos, por ejemplo:
// llamar al comando sin el parámetro de url requerido y la carga útil
await browser.myNewCommand()
/**
* resulta en el siguiente error:
* 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 (...)
* ...
*/
Llamar al comando correctamente, por ejemplo browser.myNewCommand('foo', 'bar')
, hace correctamente una solicitud WebDriver a, por ejemplo, http://localhost:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo
con una carga útil como { foo: 'bar' }
.
El parámetro url :sessionId
se sustituirá automáticamente con el id de sesión de la sesión WebDriver. Se pueden aplicar otros parámetros url pero deben definirse dentro de variables
.
Consulta ejemplos de cómo se pueden definir los comandos de protocolo en el paquete @wdio/protocols
.