カスタムコマンド
独自のコマンドセットでbrowserインスタンスを拡張したい場合は、ブラウザメソッドaddCommandが用意されています。スペックと同様に、非同期方式でコマンドを作成できます。
パラメーター
コマンド名
コマンドを定義し、ブラウザーまたは要素のスコープにアタッチされる名前。
タイプ: String
カスタム関数
コマンドが呼び出されたときに実行される関数。thisスコープは、コマンドがブラウザーまたは要素のスコープにアタッチされるかどうかに応じて、WebdriverIO.BrowserまたはWebdriverIO.Elementのいずれかになります。
タイプ: Function
ターゲットスコープ
コマンドをブラウザーまたは要素のスコープにアタッチするかどうかを決定するフラグ。trueに設定すると、コマンドは要素コマンドになります。
タイプ: Boolean
デフォルト: false
例
この例では、現在のURLとタイトルを1つの結果として返す新しいコマンドを追加する方法を示します。スコープ(this)は、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
    }
})
さらに、最後の引数としてtrueを渡すことで、独自のコマンドセットで要素インスタンスを拡張できます。この場合のスコープ(this)は、WebdriverIO.Elementオブジェクトです。
browser.addCommand("waitAndClick", async function () {
    // `this` is return value of $(selector)
    await this.waitForDisplayed()
    await this.click()
}, true)
カスタムコマンドを使用すると、頻繁に使用する特定のコマンドシーケンスを単一の呼び出しとしてまとめることができます。カスタムコマンドは、テストスイートの任意の時点で定義できます。コマンドが最初に使われる前に定義されていることを確認してください。(wdio.conf.jsのbeforeフックは、それらを作成するのに最適な場所です。)
定義すると、次のように使用できます
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) // outputs "function"
console.log(typeof elem.myCustomBrowserCommand()) // outputs "undefined"
browser.addCommand("myCustomElementCommand", () => { return 1 }, 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"
注: カスタムコマンドをチェーンする必要がある場合は、コマンドが$で終わる必要があります。
browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, true)
await browser.user$('foo').user$('bar').click()
browserスコープにあまりにも多くのカスタムコマンドを過負荷にしないように注意してください。
特定のページにバインドされるように、ページオブジェクトでカスタムロジックを定義することをお勧めします。
型定義の拡張
TypeScriptを使用すると、WebdriverIOインターフェースを簡単に拡張できます。次のように、カスタムコマンドに型を追加します
- 
型定義ファイル(例: ./src/types/wdio.d.ts)を作成します
- 
a. モジュールスタイルの型定義ファイル(import/exportと型定義ファイル内の declare global WebdriverIOを使用)を使用している場合は、tsconfig.jsonのincludeプロパティにファイルパスを含めるようにしてください。b. アンビエントスタイルの型定義ファイル(型定義ファイルにimport/exportがなく、カスタムコマンドに declare namespace WebdriverIOを使用)を使用している場合は、tsconfig.jsonにincludeセクションがないことを確認してください。これにより、includeセクションにリストされていないすべての型定義ファイルがtypescriptによって認識されなくなります。
- モジュール(import/exportを使用)
- アンビエント型定義(tsconfigにincludeなし)
{
    "compilerOptions": { ... },
    "include": [
        "./test/**/*.ts",
        "./src/types/**/*.ts"
    ]
}
{
    "compilerOptions": { ... }
}
- 実行モードに応じてコマンドの定義を追加します。
- モジュール(import/exportを使用)
- アンビエント型定義
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>
    }
}
サードパーティライブラリの統合
Promiseをサポートする外部ライブラリ(例:データベース呼び出しを行うため)を使用する場合、特定のAPIメソッドをカスタムコマンドでラップするのが統合するための良いアプローチです。
Promiseを返すと、WebdriverIOはPromiseが解決されるまで次のコマンドに進まないことを保証します。Promiseが拒否された場合、コマンドはエラーをスローします。
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) // returns response body
})
注: カスタムコマンドの結果は、返されるPromiseの結果です。
コマンドの上書き
overwriteCommandを使用して、ネイティブコマンドを上書きすることもできます。
これはフレームワークの予測不可能な動作につながる可能性があるため、お勧めしません!
全体的なアプローチはaddCommandと似ていますが、唯一の違いは、コマンド関数の最初の引数が、上書きしようとしている元の関数であることです。以下の例を参照してください。
ブラウザコマンドの上書き
/**
 * print milliseconds before pause and return its value.
 */
// 'pause'            - name of command to be overwritten
// origPauseFunction  - original pause function
browser.overwriteCommand('pause', async (origPauseFunction, ms) => {
    console.log(`sleeping for ${ms}`)
    await origPauseFunction(ms)
    return ms
})
// then use it as before
console.log(`was sleeping for ${await browser.pause(1000)}`)
要素コマンドの上書き
要素レベルでコマンドを上書きすることはほとんど同じです。overwriteCommandの3番目の引数としてtrueを渡すだけです
/**
 * 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.
 */
// 'click'            - name of command to be overwritten
// origClickFunction  - original click function
browser.overwriteCommand('click', async function (origClickFunction, { force = false } = {}) {
    if (!force) {
        try {
            // attempt to click
            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.')
                // scroll to element and click again
                await this.scrollIntoView()
                return origClickFunction()
            }
            throw err
        }
    }
    // clicking with js
    console.warn('WARN: Using force click for', this.selector)
    await browser.execute((el) => {
        el.click()
    }, this)
}, true) // don't forget to pass `true` as 3rd argument
// then use it as before
const elem = await $('body')
await elem.click()
// or pass params
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
    }]
}))
このコマンドを無効なパラメーターで呼び出すと、定義済みのプロトコルコマンドと同じエラー処理になります。例えば、
// 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 (...)
 *    ...
 */
コマンドを正しく呼び出すと(例:browser.myNewCommand('foo', 'bar'))、{ foo: 'bar' }のようなペイロードを使用して、例えばhttps://:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/fooへのWebDriverリクエストが正しく行われます。
:sessionId URLパラメータは、WebDriverセッションのセッションIDで自動的に置換されます。他のURLパラメータも適用できますが、variables内で定義する必要があります。
プロトコルコマンドの定義方法の例は、@wdio/protocols パッケージを参照してください。