Comandi Personalizzati
Se desideri estendere l'istanza browser con il tuo set di comandi, il metodo addCommand del browser è qui per te. Puoi scrivere il tuo comando in modo asincrono, proprio come nelle tue specifiche.
Parametri
Nome del Comando
Un nome che definisce il comando e che sarà collegato allo scope del browser o dell'elemento.
Tipo: String
Funzione Personalizzata
Una funzione che viene eseguita quando il comando viene chiamato. Lo scope this è WebdriverIO.Browser o WebdriverIO.Element a seconda che il comando venga collegato allo scope del browser o dell'elemento.
Tipo: Function
Opzioni
Oggetto con opzioni di configurazione che modificano il comportamento del comando personalizzato
Target Scope
Flag per decidere se collegare il comando allo scope del browser o dell'elemento. Se impostato su true il comando sarà un comando dell'elemento.
Nome Opzione: attachToElement
Tipo: Boolean
Default: false
Disattiva implicitWait
Flag per decidere se attendere implicitamente che l'elemento esista prima di chiamare il comando personalizzato.
Nome Opzione: disableElementImplicitWait
Tipo: Boolean
Default: false
Esempi
Questo esempio mostra come aggiungere un nuovo comando che restituisce l'URL e il titolo correnti come un unico risultato. Lo scope (this) è un oggetto WebdriverIO.Browser.
browser.addCommand('getUrlAndTitle', async function (customVar) {
// `this` refers to the `browser` scope
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})
Inoltre, puoi estendere l'istanza dell'elemento con il tuo set di comandi, passando true come argomento finale. Lo scope (this) in questo caso è un oggetto WebdriverIO.Element.
browser.addCommand("waitAndClick", async function () {
// `this` is return value of $(selector)
await this.waitForDisplayed()
await this.click()
}, { attachToElement: true })
Per impostazione predefinita, i comandi personalizzati degli elementi attendono che l'elemento esista prima di chiamare il comando personalizzato. Anche se la maggior parte delle volte questo è desiderato, se non lo è, può essere disabilitato con disableImplicitWait:
browser.addCommand("waitAndClick", async function () {
// `this` is return value of $(selector)
await this.waitForExists()
await this.click()
}, { attachToElement: true, disableElementImplicitWait: true })
I comandi personalizzati ti offrono l'opportunità di raggruppare una specifica sequenza di comandi che utilizzi frequentemente in una singola chiamata. Puoi definire comandi personalizzati in qualsiasi punto della tua suite di test; assicurati solo che il comando sia definito prima del suo primo utilizzo. (L'hook before nel tuo wdio.conf.js è un buon posto per crearli.)
Una volta definiti, puoi utilizzarli come segue:
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 registri un comando personalizzato nello scope browser, il comando non sarà accessibile per gli elementi. Allo stesso modo, se registri un comando nello scope dell'elemento, non sarà accessibile nello scope browser:
browser.addCommand("myCustomBrowserCommand", () => { return 1 })
const elem = await $('body')
console.log(typeof browser.myCustomBrowserCommand) // outputs "function"
console.log(typeof elem.myCustomBrowserCommand()) // outputs "undefined"
browser.addCommand("myCustomElementCommand", () => { return 1 }, { attachToElement: true })
const elem2 = await $('body')
console.log(typeof browser.myCustomElementCommand) // outputs "undefined"
console.log(await elem2.myCustomElementCommand('foobar')) // outputs "1"
const elem3 = await $('body')
elem3.addCommand("myCustomElementCommand2", () => { return 2 })
console.log(typeof browser.myCustomElementCommand2) // outputs "undefined"
console.log(await elem3.myCustomElementCommand2('foobar')) // outputs "2"
Nota: Se hai bisogno di concatenare un comando personalizzato, il comando dovrebbe terminare con $,
browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, { attachToElement: true })
await browser.user$('foo').user$('bar').click()
Fai attenzione a non sovraccaricare lo scope browser con troppi comandi personalizzati.
Consigliamo di definire la logica personalizzata negli object page, in modo che siano vincolati a una pagina specifica.
Multiremote
addCommand funziona in modo simile per multiremote, tranne per il fatto che il nuovo comando si propagherà alle istanze figlie. Devi fare attenzione quando usi l'oggetto this poiché il browser multiremote e le sue istanze figlie hanno this diversi.
Questo esempio mostra come aggiungere un nuovo comando per multiremote.
import { multiRemoteBrowser } from '@wdio/globals'
multiRemoteBrowser.addCommand('getUrlAndTitle', async function (this: WebdriverIO.MultiRemoteBrowser, customVar: any) {
// `this` refers to:
// - MultiRemoteBrowser scope for browser
// - Browser scope for instances
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
}
*/
Estendere le Definizioni di Tipo
Con TypeScript, è facile estendere le interfacce WebdriverIO. Aggiungi tipi ai tuoi comandi personalizzati in questo modo:
-
Crea un file di definizione di tipo (ad esempio,
./src/types/wdio.d.ts) -
a. Se usi un file di definizione di tipo in stile modulo (utilizzando import/export e
declare global WebdriverIOnel file di definizione del tipo), assicurati di includere il percorso del file nella proprietàincludeditsconfig.json.b. Se usi file di definizione di tipo in stile ambiente (nessun import/export nei file di definizione di tipo e
declare namespace WebdriverIOper i comandi personalizzati), assicurati chetsconfig.jsonnon contenga alcuna sezioneinclude, poiché questo farà sì che tutti i file di definizione di tipo non elencati nella sezioneincludenon siano riconosciuti da TypeScript.
- Modules (using import/export)
- Ambient Type Definitions (no tsconfig include)
{
"compilerOptions": { ... },
"include": [
"./test/**/*.ts",
"./src/types/**/*.ts"
]
}
{
"compilerOptions": { ... }
}
- Aggiungi definizioni per i tuoi comandi in base alla tua modalità di esecuzione.
- Modules (using import/export)
- Ambient Type Definitions
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>
}
}
Integrare Librerie di Terze Parti
Se utilizzi librerie esterne (ad esempio, per effettuare chiamate al database) che supportano le promesse, un buon approccio per integrarle è avvolgere determinati metodi API con un comando personalizzato.
Quando restituisci la promessa, WebdriverIO garantisce che non continui con il comando successivo fino a quando la promessa non viene risolta. Se la promessa viene rifiutata, il comando genererà un errore.
browser.addCommand('makeRequest', async (url) => {
const response = await fetch(url)
return await response.json()
})
Quindi, usalo nelle tue specifiche di test WDIO:
it('execute external library in a sync way', async () => {
await browser.url('...')
const body = await browser.makeRequest('http://...')
console.log(body) // returns response body
})
Nota: Il risultato del tuo comando personalizzato è il risultato della promessa che restituisci.
Sovrascrivere i Comandi
Puoi anche sovrascrivere i comandi nativi con overwriteCommand.
Non è consigliato farlo, perché potrebbe portare a comportamenti imprevedibili del framework!
L'approccio generale è simile a addCommand, l'unica differenza è che il primo argomento nella funzione del comando è la funzione originale che stai per sovrascrivere. Si prega di vedere alcuni esempi di seguito.
Sovrascrivere i Comandi del Browser
/**
* Print milliseconds before pause and return its value.
*
* @param pause - name of command to be overwritten
* @param this of func - the original browser instance on which the function was called
* @param originalPauseFunction of func - the original pause function
* @param ms of func - the actual parameters passed
*/
browser.overwriteCommand('pause', async function (this, originalPauseFunction, ms) {
console.log(`sleeping for ${ms}`)
await originalPauseFunction(ms)
return ms
})
// then use it as before
console.log(`was sleeping for ${await browser.pause(1000)}`)
Sovrascrivere i Comandi degli Elementi
Sovrascrivere i comandi a livello di elemento è quasi lo stesso. Basta passare true come terzo argomento a overwriteCommand:
/**
* Attempt to scroll to element if it is not clickable.
* Pass { force: true } to click with JS even if element is not visible or clickable.
* Show that the original function argument type can be kept with `options?: ClickOptions`
*
* @param this of func - the element on which the original function was called
* @param originalClickFunction of func - the original pause function
* @param options of func - the actual parameters passed
*/
browser.overwriteCommand(
'click',
async function (this, originalClickFunction, options?: ClickOptions & { force?: boolean }) {
const { force, ...restOptions } = options || {}
if (!force) {
try {
// attempt to click
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.')
// scroll to element and click again
await this.scrollIntoView()
return originalClickFunction(options)
}
throw err
}
}
// clicking with js
console.warn('WARN: Using force click for', this.selector)
await browser.execute((el) => {
el.click()
}, this)
},
{ attachToElement: true }, // Don't forget to attach it to the element
)
// then use it as before
const elem = await $('body')
await elem.click()
// or pass params
await elem.click({ force: true })
Aggiungere Altri Comandi WebDriver
Se stai utilizzando il protocollo WebDriver ed esegui test su una piattaforma che supporta comandi aggiuntivi non definiti da nessuna delle definizioni di protocollo in @wdio/protocols puoi aggiungerli manualmente attraverso l'interfaccia addCommand. Il pacchetto webdriver offre un wrapper di comando che consente di registrare questi nuovi endpoint allo stesso modo degli altri comandi, fornendo gli stessi controlli di parametri e gestione degli errori. Per registrare questo nuovo endpoint importa il wrapper di comando e registra un nuovo comando con esso come segue:
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
}]
}))
Chiamare questo comando con parametri non validi comporta la stessa gestione degli errori dei comandi di protocollo predefiniti, ad es.:
// call command without required url parameter and payload
await browser.myNewCommand()
/**
* results in the following 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 (...)
* ...
*/
Chiamando correttamente il comando, ad esempio browser.myNewCommand('foo', 'bar'), effettua correttamente una richiesta WebDriver a es. http://localhost:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo con un payload come { foo: 'bar' }.
Il parametro url :sessionId verrà automaticamente sostituito con l'ID sessione della sessione WebDriver. È possibile applicare altri parametri URL, ma devono essere definiti all'interno di variables.
Vedi esempi di come possono essere definiti i comandi di protocollo nel pacchetto @wdio/protocols.