カスタムコマンド
独自のコマンドセットで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' }
のようなペイロードを使用して、例えばhttp://localhost:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo
へのWebDriverリクエストが正しく行われます。
:sessionId
URLパラメータは、WebDriverセッションのセッションIDで自動的に置換されます。他のURLパラメータも適用できますが、variables
内で定義する必要があります。
プロトコルコマンドの定義方法の例は、@wdio/protocols
パッケージを参照してください。