allow to use custom commands and overwrite command before session start
This commit is contained in:
parent
adb037129c
commit
785d0cf78c
10 changed files with 97 additions and 68 deletions
|
|
@ -8,6 +8,8 @@ const getWdioMock = () => {
|
|||
debug: vi.fn(),
|
||||
on: vi.fn(),
|
||||
sessionId: 'fakeid',
|
||||
addCommand: vi.fn(),
|
||||
overwriteCommand: vi.fn(),
|
||||
react$: vi.fn(),
|
||||
react$$: vi.fn(),
|
||||
custom$: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -273,11 +273,28 @@ export default class Runner extends EventEmitter {
|
|||
caps: Capabilities.RemoteCapability
|
||||
) {
|
||||
try {
|
||||
/**
|
||||
* get all custom or overwritten commands users tried to register before the
|
||||
* test started, e.g. after all imports
|
||||
*/
|
||||
const customStubCommands: [string, (...args: any[]) => any, boolean][] = (this._browser as any | undefined)?.customCommands || []
|
||||
const overwrittenCommands: [any, (...args: any[]) => any, boolean][] = (this._browser as any | undefined)?.overwrittenCommands || []
|
||||
|
||||
this._browser = await initialiseInstance(config, caps, this._isMultiremote)
|
||||
_setGlobal('browser', this._browser, config.injectGlobals)
|
||||
_setGlobal('driver', this._browser, config.injectGlobals)
|
||||
_setGlobal('expect', expect, config.injectGlobals)
|
||||
|
||||
/**
|
||||
* re-assign previously registered custom commands to the actual instance
|
||||
*/
|
||||
for (const params of customStubCommands) {
|
||||
this._browser.addCommand(...params)
|
||||
}
|
||||
for (const params of overwrittenCommands) {
|
||||
this._browser.overwriteCommand(...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* import and set options for `expect-webdriverio` assertion lib once
|
||||
* the browser was initiated
|
||||
|
|
|
|||
|
|
@ -91,7 +91,12 @@ export async function initialiseInstance (
|
|||
}
|
||||
|
||||
const browser = await multiremote(options, config)
|
||||
for (let browserName of Object.keys(capabilities)) {
|
||||
|
||||
/**
|
||||
* only attach to global environment if `injectGlobals` is set to true
|
||||
*/
|
||||
const browserNames = config.injectGlobals ? Object.keys(capabilities) : []
|
||||
for (let browserName of browserNames) {
|
||||
// @ts-ignore allow random global browser names
|
||||
global[browserName] = browser[browserName]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -502,6 +502,27 @@ describe('wdio-runner', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('_startSession', () => {
|
||||
it('can start a session', async () => {
|
||||
const runner = new WDIORunner()
|
||||
const browser = await runner['_startSession']({} as any, {} as any)
|
||||
expect(typeof browser?.deleteSession).toBe('function')
|
||||
expect(_setGlobal).toBeCalledTimes(3)
|
||||
expect(setOptions).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it('transfers custom commands from old instance to new one', async () => {
|
||||
const runner = new WDIORunner()
|
||||
runner['_browser'] = {
|
||||
customCommands: [[1, 2, 3]],
|
||||
overwrittenCommands: [[3, 2, 1]]
|
||||
} as any
|
||||
const browser = await runner['_startSession']({} as any, {} as any)
|
||||
expect(browser?.addCommand).toBeCalledWith(1, 2, 3)
|
||||
expect(browser?.overwriteCommand).toBeCalledWith(3, 2, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_shutdown', () => {
|
||||
it('should emit exit', async () => {
|
||||
const runner = new WDIORunner()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { capabilitiesEnvironmentDetector } from '@wdio/utils'
|
||||
import type { Capabilities, Options } from '@wdio/types'
|
||||
import type { AttachOptions } from 'webdriver'
|
||||
|
||||
/**
|
||||
* these commands can be used outside test scope and may be used accidentally by user before browser session is started
|
||||
*/
|
||||
const WARN_ON_COMMANDS = ['addCommand', 'overwriteCommand']
|
||||
|
||||
/**
|
||||
* create `browser` object with capabilities and environment flags before session is started
|
||||
|
|
@ -17,12 +11,18 @@ export default class ProtocolStub {
|
|||
(options.capabilities || {}) as unknown as Capabilities.DesiredCapabilities
|
||||
)
|
||||
|
||||
const browser = addCommands({
|
||||
const browser: any = {
|
||||
options,
|
||||
capabilities,
|
||||
requestedCapabilities: capabilities,
|
||||
customCommands: [], // internally used to transfer custom commands to the actual protocol instance
|
||||
overwrittenCommands: [], // internally used to transfer overwritten commands to the actual protocol instance
|
||||
commandList: [],
|
||||
...capabilitiesEnvironmentDetector(capabilities, (options as any)._automationProtocol || 'webdriver')
|
||||
})
|
||||
}
|
||||
|
||||
browser.options = options
|
||||
browser.addCommand = (...args: any) => browser.customCommands.push(args)
|
||||
browser.overwriteCommand = (...args: any) => browser.overwrittenCommands.push(args)
|
||||
return browser
|
||||
}
|
||||
|
||||
|
|
@ -34,35 +34,20 @@ export default class ProtocolStub {
|
|||
throw new Error('Protocol Stub: Make sure to start webdriver or devtools session before reloading it.')
|
||||
}
|
||||
|
||||
static attachToSession (
|
||||
options: AttachOptions,
|
||||
modifier?: (...args: any[]) => any
|
||||
) {
|
||||
static attachToSession (options: never, modifier?: Function) {
|
||||
if (options || !modifier) {
|
||||
return ProtocolStub.newSession(options as any)
|
||||
throw new Error('You are trying to attach to a protocol stub, this should never occur, please file an issue.')
|
||||
}
|
||||
|
||||
/**
|
||||
* MultiRemote
|
||||
* MultiRemote is needed
|
||||
*/
|
||||
return addCommands(modifier({
|
||||
return modifier({
|
||||
commandList: []
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* provide better visibility to users that want to add / overwrite commands
|
||||
* before session is started
|
||||
* @param {object} browser
|
||||
*/
|
||||
function addCommands (browser: Record<string, any>) {
|
||||
WARN_ON_COMMANDS.forEach((commandName) => {
|
||||
browser[commandName] = commandNotAvailable(commandName)
|
||||
})
|
||||
return browser
|
||||
}
|
||||
|
||||
/**
|
||||
* transform capabilities provided by user to look like
|
||||
* capabilities returned by WebDriver
|
||||
|
|
@ -85,11 +70,3 @@ function emulateSessionCapabilities (caps: Capabilities.DesiredCapabilities) {
|
|||
|
||||
return capabilities
|
||||
}
|
||||
|
||||
/**
|
||||
* warn user to avoid usage of command before browser session is started.
|
||||
* @param {string} commandName
|
||||
*/
|
||||
function commandNotAvailable (commandName: string) {
|
||||
return () => { throw new Error(`Unable to use '${commandName}' before browser session is started.`) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,10 +187,6 @@ describe('WebdriverIO module interface', () => {
|
|||
|
||||
expect(browser.sessionId).toBeUndefined()
|
||||
expect(browser.capabilities).toEqual({ browserName: 'chrome', chrome: true })
|
||||
// @ts-ignore test types
|
||||
expect(() => browser.addCommand()).toThrow()
|
||||
// @ts-ignore test types
|
||||
expect(() => browser.overwriteCommand()).toThrow()
|
||||
|
||||
const flags: any = {}
|
||||
Object.entries(browser).forEach(([key, value]) => {
|
||||
|
|
|
|||
|
|
@ -59,13 +59,6 @@ test('should properly create stub instance', async () => {
|
|||
expect(browser.browserB).toBeDefined()
|
||||
expect(browser.browserA.$).toBeUndefined()
|
||||
expect(browser.browserA.$).toBeUndefined()
|
||||
|
||||
// @ts-expect-error invalid params
|
||||
expect(() => browser.addCommand()).toThrow()
|
||||
// @ts-expect-error invalid params
|
||||
expect(() => browser.browserA.addCommand()).toThrow()
|
||||
// @ts-expect-error invalid params
|
||||
expect(() => browser.browserB.overwriteCommand()).toThrow()
|
||||
})
|
||||
|
||||
test('should allow to call on elements', async () => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('newSession', () => {
|
|||
foo: 'bar'
|
||||
}
|
||||
})
|
||||
expect(Object.keys(session)).toHaveLength(10)
|
||||
expect(Object.keys(session)).toHaveLength(14)
|
||||
expect(session.isAndroid).toBe(false)
|
||||
expect(session.isChrome).toBe(false)
|
||||
expect(session.isFirefox).toBe(false)
|
||||
|
|
@ -31,25 +31,22 @@ describe('newSession', () => {
|
|||
platformName: 'iOS',
|
||||
foo: 'bar'
|
||||
})
|
||||
expect(() => session.addCommand()).toThrow()
|
||||
expect(() => session.overwriteCommand()).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('attachToSession', () => {
|
||||
it('should return newSession if not multiremote', async () => {
|
||||
it('should throw if not multiremote', async () => {
|
||||
const modifier = vi.fn()
|
||||
const session = await ProtocolStub.attachToSession({
|
||||
expect(() => ProtocolStub.attachToSession({
|
||||
sessionId: '1234',
|
||||
capabilities: { browserName: 'chrome' }
|
||||
}, modifier)
|
||||
} as never, modifier)).toThrow()
|
||||
expect(modifier).not.toBeCalled()
|
||||
expect(session.isChrome).toBe(true)
|
||||
})
|
||||
|
||||
it('should return newSession if modifier was not passed', async () => {
|
||||
const session = await ProtocolStub.attachToSession({ sessionId: '1234' })
|
||||
expect(session.capabilities).toEqual({})
|
||||
expect(() => ProtocolStub.attachToSession({ sessionId: '1234' } as never))
|
||||
.toThrow()
|
||||
})
|
||||
|
||||
it('should call modifier if multiremote', async () => {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,32 @@ id: customcommands
|
|||
title: Custom Commands
|
||||
---
|
||||
|
||||
## Adding custom commands
|
||||
|
||||
If you want to extend the `browser` instance with your own set of commands, the browser method `addCommand` is here for you. You can write your command in a asynchronous way, just as in your specs.
|
||||
|
||||
This example shows how to add a new command that returns the current URL and title as one result. The scope (`this`) is a [`WebdriverIO.Browser](/docs/api/browser) object.
|
||||
## Parameters
|
||||
|
||||
### Command Name
|
||||
|
||||
A name that defines the command and will be attached to the browser or element scope.
|
||||
|
||||
Type: `String`
|
||||
|
||||
### Custom Function
|
||||
|
||||
A function that is being executed when the command is called. The `this` scope is either [`WebdriverIO.Browser`](/docs/api/browser) or [`WebdriverIO.Element`](/docs/api/element) depending whether the command gets attached to the browser or element scope.
|
||||
|
||||
Type: `Function`
|
||||
|
||||
### Target Scope
|
||||
|
||||
Flag to decide whether to attach the command to the browser or element scope. If set to `true` the command will be an element command.
|
||||
|
||||
Type: `Boolean`<br />
|
||||
Default: `false`
|
||||
|
||||
## Examples
|
||||
|
||||
This example shows how to add a new command that returns the current URL and title as one result. The scope (`this`) is a [`WebdriverIO.Browser`](/docs/api/browser) object.
|
||||
|
||||
```js
|
||||
browser.addCommand('getUrlAndTitle', async function (customVar) {
|
||||
|
|
@ -20,7 +41,7 @@ browser.addCommand('getUrlAndTitle', async function (customVar) {
|
|||
})
|
||||
```
|
||||
|
||||
Additionally, you can extend the element instance with your own set of commands, by passing `true` as the final argument. The scope (`this`) in this case is a [`WebdriverIO.Element](/docs/api/element) object.
|
||||
Additionally, you can extend the element instance with your own set of commands, by passing `true` as the final argument. The scope (`this`) in this case is a [`WebdriverIO.Element`](/docs/api/element) object.
|
||||
|
||||
```js
|
||||
browser.addCommand("waitAndClick", async function () {
|
||||
|
|
@ -167,7 +188,7 @@ declare namespace WebdriverIO {
|
|||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Integrate 3rd party libraries
|
||||
## Integrate 3rd Party Libraries
|
||||
|
||||
If you use external libraries (e.g., to do database calls) that support promises, a nice approach to integrate them is to wrap certain API methods with a custom command.
|
||||
|
||||
|
|
@ -193,7 +214,7 @@ it('execute external library in a sync way', async () => {
|
|||
|
||||
**Note:** The result of your custom command is the result of the promise you return.
|
||||
|
||||
## Overwriting native commands
|
||||
## Overwriting Commands
|
||||
|
||||
You can also overwrite native commands with `overwriteCommand`.
|
||||
|
||||
|
|
@ -201,7 +222,7 @@ It is not recommended to do this, because it may lead to unpredictable behavior
|
|||
|
||||
The overall approach is similar to `addCommand`, the only difference is that the first argument in the command function is the original function that you are about to overwrite. Please see some examples below.
|
||||
|
||||
### Overwriting browser commands
|
||||
### Overwriting Browser Commands
|
||||
|
||||
```js
|
||||
/**
|
||||
|
|
@ -219,7 +240,7 @@ browser.overwriteCommand('pause', async (origPauseFunction, ms) => {
|
|||
console.log(`was sleeping for ${await browser.pause(1000)}`)
|
||||
```
|
||||
|
||||
#### Overwriting element commands
|
||||
### Overwriting Element Commands
|
||||
|
||||
Overwriting commands on element level is almost the same. Simply pass `true` as the third argument to `overwriteCommand`:
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ In addition to that the following commands are available:
|
|||
|
||||
| Name | Parameters | Details |
|
||||
| ---- | ---------- | ------- |
|
||||
| `addCommand` | - `commandName` (Type: `String`)<br />- `fn` (Type: `Function`) | Allows to define custom commands that can be called from the browser object for compisition purposes. Read more in the [Custom Command](/docs/customcommands) guide. |
|
||||
| `overwriteCommand` | - `commandName` (Type: `String`)<br />- `fn` (Type: `Function`) | Allows to overwite any browser command with custom functionality. Use carefully as it can confuse framework users. Read more in the [Custom Command](/docs/customcommands#overwriting-native-commands) guide. |
|
||||
| `addCommand` | - `commandName` (Type: `String`)<br />- `fn` (Type: `Function`)<br />- `attachToElement` (Type: `boolean`) | Allows to define custom commands that can be called from the browser object for compisition purposes. Read more in the [Custom Command](/docs/customcommands) guide. |
|
||||
| `overwriteCommand` | - `commandName` (Type: `String`)<br />- `fn` (Type: `Function`)<br />- `attachToElement` (Type: `boolean`) | Allows to overwite any browser command with custom functionality. Use carefully as it can confuse framework users. Read more in the [Custom Command](/docs/customcommands#overwriting-native-commands) guide. |
|
||||
|
||||
## Remarks
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue