import nock from 'nock'
import { v4 as uuidv4 } from 'uuid'
import type { Services } from '@wdio/types'
import WebDriverMock from './WebDriverMock.js'
import { NO_SUCH_ELEMENT } from './constants.js'
import { newSession, deleteSession } from './mocks/newSession.js'
const ELEMENT_ID = '401c0039-3306-6a46-a98d-f5939870a249'
const ELEMENT_REFETCHED = '80d860d0-b829-f540-812e-7078eb983795'
const ELEMENT_ALT = '8bf4d107-a363-40d1-b823-d94bdbc58afb'
const ELEM_PROP = 'element-6066-11e4-a52e-4f735466cecf'
export default class WebdriverMockService implements Services.ServiceInstance {
private _browser?: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
private _mock = new WebDriverMock()
constructor () {
this.init()
}
init() {
this._mock.command.status().times(Infinity).reply(200, { value: {} })
this._mock.command.newSession().times(Infinity).reply(200, () => {
newSession.value.sessionId = uuidv4()
return newSession
})
this._mock.command.deleteSession().times(2).reply(200, deleteSession)
this._mock.command.getTitle().times(Infinity).reply(200, { value: 'Mock Page Title' })
this._mock.command.getUrl().times(Infinity).reply(200, { value: 'https://mymockpage.com' })
this._mock.command.getElementRect(ELEMENT_ID).times(2).reply(200, { value: { width: 1, height: 2, x: 3, y: 4 } })
this._mock.command.getElementRect(ELEMENT_ALT).times(3).reply(200, { value: { width: 10, height: 20, x: 30, y: 40 } })
this._mock.command.getElementRect(ELEMENT_REFETCHED).times(1).reply(200, { value: { width: 1, height: 2, x: 3, y: 4 } })
this._mock.command.getElementCSSValue(ELEMENT_ID, 'display').times(Infinity).reply(200, { value: 'block' })
this._mock.command.getLogTypes().reply(200, { value: [] })
}
beforeSession(config: WebdriverIO.Config): void {
config.hostname = 'localhost'
config.port = 4444
}
before (
caps: unknown,
specs: unknown,
browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
) {
this._browser = browser
* register request interceptors for specific scenarios
*/
this._browser.addCommand('waitForElementScenario', this.waitForElementScenario.bind(this))
this._browser.addCommand('isNeverDisplayedScenario', this.isNeverDisplayedScenario.bind(this))
this._browser.addCommand('isEventuallyDisplayedScenario', this.isEventuallyDisplayedScenario.bind(this))
this._browser.addCommand('staleElementRefetchScenario', this.staleElementRefetchScenario.bind(this))
this._browser.addCommand('customCommandScenario', this.customCommandScenario.bind(this))
this._browser.addCommand('customSelectorScenario', this.customSelectorScenario.bind(this))
this._browser.addCommand('waitForDisplayedScenario', this.waitForDisplayedScenario.bind(this))
this._browser.addCommand('cucumberScenario', this.cucumberScenario.bind(this))
this._browser.addCommand('clickScenario', this.clickScenario.bind(this))
this._browser.addCommand('isExistingScenario', this.isExistingScenario.bind(this))
this._browser.addCommand('isNotExistingScenario', this.isNotExistingScenario.bind(this))
this._browser.addCommand('multiremoteFetch', this.multiremoteFetch.bind(this))
this._browser.addCommand('asyncIterationScenario', this.asyncIterationScenario.bind(this))
this._browser.addCommand('parentElementChaining', this.parentNextPreviousElementChaining.bind(this))
this._browser.addCommand('refetchElementScenario', this.refetchElementScenario.bind(this))
this._browser.addCommand('waitForExistScenario', this.waitForExistScenario.bind(this))
this._browser.addCommand('executeMemLeakScenario', this.executeMemLeakScenario.bind(this))
this._browser.addCommand('requestRetryScenario', this.requestRetryScenario.bind(this))
}
clickScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().times(2).reply(200, { value: elemResponse })
this._mock.command.elementClick(ELEMENT_ID).once().reply(200, { value: null })
}
isExistingScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().times(1).reply(200, { value: elemResponse })
this._mock.command.findElementFromElement(ELEMENT_ID).times(2).reply(200, { value: elemResponse })
this._mock.command.findElementsFromElement(ELEMENT_ID).times(2).reply(200, { value: [elemResponse] })
}
isNotExistingScenario() {
this.nockReset()
this._mock.command.findElement().reply(404, NO_SUCH_ELEMENT)
}
waitForElementScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().once().reply(404, NO_SUCH_ELEMENT)
this._mock.command.findElement().times(2).reply(200, { value: elemResponse })
this._mock.command.findElements().times(5).reply(200, { value: [] })
this._mock.command.findElements().reply(200, { value: [elemResponse] })
this._mock.command.elementClick(ELEMENT_ID).once().reply(200, { value: null })
}
isNeverDisplayedScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().times(2).reply(404, NO_SUCH_ELEMENT)
this._mock.command.findElement().times(2).reply(200, { value: elemResponse })
this._mock.command.executeScript(ELEMENT_ID).once().reply(200, { value: false })
}
isEventuallyDisplayedScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().times(1).reply(404, NO_SUCH_ELEMENT)
this._mock.command.findElement().times(2).reply(200, { value: elemResponse })
this._mock.command.executeScript(ELEMENT_ID).once().reply(200, { value: true })
}
staleElementRefetchScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elem2Response = { [ELEM_PROP]: ELEMENT_REFETCHED }
this._mock.command.findElement().once().reply(200, { value: elemResponse })
this._mock.command.findElement().once().reply(404, NO_SUCH_ELEMENT)
this._mock.command.findElement().times(4).reply(200, { value: elem2Response })
this._mock.command.elementClick(ELEMENT_ID).once().reply(200, { value: null })
this._mock.command.elementClick(ELEMENT_ID).times(8).reply(500, {
value: {
error: 'stale element reference',
message: 'element is not attached to the page document'
}
})
this._mock.command.elementClick(ELEMENT_REFETCHED).times(4).reply(200, { value: null })
this._mock.command.findElements().times(2).reply(200, { value: [] })
this._mock.command.findElements().times(4).reply(200, { value: [elem2Response] })
}
refetchElementScenario() {
const elemResponse1 = { [ELEM_PROP]: '1' }
const elemResponse2 = { [ELEM_PROP]: '2' }
const elemResponse3 = { [ELEM_PROP]: '3' }
const elemResponse4 = { [ELEM_PROP]: '4' }
this._mock.command.getElementText('1').reply(200, { value: 'some element text 1' })
this._mock.command.getElementText('2').reply(200, { value: 'some element text 2' })
this._mock.command.getElementText('3').reply(200, { value: 'some element text 3' })
this._mock.command.getElementText('4').reply(200, { value: 'some element text 4' })
this._mock.command.findElements().times(4).reply(200, { value: [] })
this._mock.command.findElements().times(1).reply(200, { value: [elemResponse1] })
this._mock.command.findElements().times(1).reply(200, { value: [elemResponse1, elemResponse2] })
this._mock.command.findElements().times(1).reply(200, { value: [elemResponse1, elemResponse2, elemResponse3] })
this._mock.command.findElements().times(1).reply(200, { value: [elemResponse1, elemResponse2, elemResponse3, elemResponse4] })
}
waitForExistScenario() {
const elemResponse = { [ELEM_PROP]: '1' }
this._mock.command.findElement().times(1).reply(404, NO_SUCH_ELEMENT)
this._mock.command.findElement().times(1).reply(200, { value: elemResponse })
this._mock.command.findElements().times(4).reply(200, { value: [] })
this._mock.command.findElements().times(2).reply(200, { value: [elemResponse] })
}
asyncIterationScenario () {
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elem2Response = { [ELEM_PROP]: ELEMENT_REFETCHED }
this._mock.command.findElements().reply(200, { value: [elemResponse, elem2Response] })
this._mock.command.getElementText(ELEMENT_ID).reply(200, { value: 'some element text' })
this._mock.command.getElementText(ELEMENT_REFETCHED).reply(200, { value: 'some other element text' })
return [ELEMENT_ID, ELEMENT_REFETCHED]
}
parentNextPreviousElementChaining () {
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elemParentResponse = { [ELEM_PROP]: ELEMENT_REFETCHED }
this._mock.command.findElement().reply(200, { value: elemResponse })
this._mock.command.executeScript().reply(200, { value: elemParentResponse })
this._mock.command.getElementText(ELEMENT_REFETCHED).reply(200, { value: 'some element text' })
}
multiremoteFetch () {
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elem2Response = { [ELEM_PROP]: ELEMENT_REFETCHED }
this._mock.command.findElement().twice().reply(200, { value: elemResponse })
this._mock.command.findElementFromElement(ELEMENT_ID).twice().reply(200, { value: elem2Response })
this._mock.command.elementClick(ELEMENT_REFETCHED).twice().reply(200, { value: null })
}
customCommandScenario(times = 1) {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elemAltResponse = { [ELEM_PROP]: ELEMENT_ALT }
this._mock.command.findElement().times(times).reply(200, { value: elemResponse })
this._mock.command.findElement().times(times).reply(200, { value: elemAltResponse })
this._mock.command.executeScript().times(times).reply(200, { value: '2' })
this._mock.command.deleteAllCookies().times(times).reply(200, { value: 'deleteAllCookies' })
}
customSelectorScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
const elemAltResponse = { [ELEM_PROP]: ELEMENT_ALT }
this._mock.command.findElement().reply(200, { value: elemResponse })
this._mock.command.executeScript().reply(200, { value: elemAltResponse })
this._mock.command.findElementFromElement(ELEMENT_ALT).reply(200, { value: elemResponse })
}
waitForDisplayedScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.findElement().once().reply(200, { value: elemResponse })
this._mock.command.executeScript(ELEMENT_ID).times(4).reply(200, { value: false })
this._mock.command.executeScript(ELEMENT_ID).once().reply(200, { value: true })
}
cucumberScenario() {
this.nockReset()
const elemResponse = { [ELEM_PROP]: ELEMENT_ID }
this._mock.command.navigateTo().reply(200, { value: null })
this._mock.command.findElement().times(4).reply(200, { value: elemResponse })
this._mock.command.elementClick(ELEMENT_ID).reply(200, { value: null })
}
executeMemLeakScenario(executeCalls: number) {
this.nockReset()
this._mock.command.executeScript().times(executeCalls).reply(200, {
statusCode: 200,
value: 'mockResponse',
})
return () => this.nockReset()
}
requestRetryScenario() {
this.nockReset()
this._mock.command.navigateTo().times(3).reply(500, {})
this._mock.command.navigateTo().reply(200, { value: null })
}
nockReset() {
nock.cleanAll()
nock.abortPendingRequests()
this.init()
}
}
* export for 3rd party usage
*/
export { WebDriverMock }
export const launcher = class WebdriverMockLauncher {
onPrepare(config: WebdriverIO.Config) {
config.hostname = 'localhost'
config.port = 4444
config.runnerEnv = {
...(config.runnerEnv || {}),
WDIO_USE_NATIVE_FETCH: 'true'
}
}
}