Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removal of the chainable promise types #12386

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/wdio-browser-runner/src/browser/expect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect, type MatcherContext, type ExpectationResult, type SyncExpectationResult } from 'expect'
import { MESSAGE_TYPES, type Workers } from '@wdio/types'
import { $ } from '@wdio/globals'
import type { ChainablePromiseElement } from 'webdriverio'

import { getCID } from './utils.js'
import { WDIO_EVENT_NAME } from '../constants.js'
Expand Down Expand Up @@ -31,7 +30,7 @@ const COMMAND_TIMEOUT = 30 * 1000 // 30s
* @returns a matcher result computed in the Node.js environment
*/
function createMatcher (matcherName: string) {
return async function (this: MatcherContext, context: WebdriverIO.Browser | WebdriverIO.Element | ChainablePromiseElement<WebdriverIO.Element> | ChainablePromiseArray, ...args: any[]) {
return async function (this: MatcherContext, context: WebdriverIO.Browser | Awaited<WebdriverIO.Element> | Awaited<WebdriverIO.ElementArray>, ...args: any[]) {
const cid = getCID()
if (!import.meta.hot || !cid) {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/webdriverio/src/commands/browser/$$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Selector } from '../../types.js'

/**
* The `$$` command is a short and handy way in order to fetch multiple elements on the page.
* It returns a `ChainablePromiseArray` containing a set of WebdriverIO elements.
* It returns an array containing a set of WebdriverIO elements.
*
* Using the wdio testrunner this command is a global variable, see [Globals](https://webdriver.io/docs/api/globals)
* for more information. Using WebdriverIO within a [standalone](https://webdriver.io/docs/setuptypes#standalone-mode)
Expand Down
2 changes: 1 addition & 1 deletion packages/webdriverio/src/commands/element/$$.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* The `$$` command is a short and handy way in order to fetch multiple elements on the page.
* It returns a `ChainablePromiseArray` containing a set of WebdriverIO elements.
* It returns an array containing a set of WebdriverIO elements.
*
* :::info
*
Expand Down
63 changes: 2 additions & 61 deletions packages/webdriverio/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,46 +18,11 @@ type $ElementCommands = typeof ElementCommands
type ElementQueryCommands = '$' | 'custom$' | 'shadow$' | 'react$'
type ElementsQueryCommands = '$$' | 'custom$$' | 'shadow$$' | 'react$$'
type ChainablePrototype = {
[K in ElementQueryCommands]: (...args: Parameters<$ElementCommands[K]>) => ChainablePromiseElement<ThenArg<ReturnType<$ElementCommands[K]>>>
[K in ElementQueryCommands]: (...args: Parameters<$ElementCommands[K]>) => Awaited<ThenArg<ReturnType<$ElementCommands[K]>>>
} & {
[K in ElementsQueryCommands]: (...args: Parameters<$ElementCommands[K]>) => ChainablePromiseArray<ThenArg<ReturnType<$ElementCommands[K]>>>
[K in ElementsQueryCommands]: (...args: Parameters<$ElementCommands[K]>) => Awaited<ThenArg<ReturnType<$ElementCommands[K]>>>
}

type AsyncElementProto = {
[K in keyof Omit<$ElementCommands, keyof ChainablePrototype>]: OmitThisParameter<$ElementCommands[K]>
} & ChainablePrototype

interface ChainablePromiseBaseElement {
/**
* WebDriver element reference
*/
elementId: Promise<string>
/**
* parent of the element if fetched via `$(parent).$(child)`
*/
parent: Promise<WebdriverIO.Element | WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser>
/**
* selector used to fetch this element, can be
* - undefined if element was created via `$({ 'element-6066-11e4-a52e-4f735466cecf': 'ELEMENT-1' })`
* - a string if `findElement` was used and a reference was found
* - or a functin if element was found via e.g. `$(() => document.body)`
*/
selector: Promise<Selector>
/**
* Error message in case element fetch was not successful
*/
error?: Promise<Error>
/**
* index of the element if fetched with `$$`
*/
index?: Promise<number>
}
export interface ChainablePromiseElement<T> extends
ChainablePromiseBaseElement,
AsyncElementProto,
Promise<T>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of only removing the Promise<T> part. I am not sure if removing ChainablePromiseElement would be a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that my colleagues have no clue what ChainablePromiseElement is and therefore just await it anyway and I cannot blame them. I think having WebdriverIO.Element as type makes much more sense, what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the noticeable difference is that:

const chainablePromiseElement = $('elem')
console.log(chainablePromiseElement.selector) // outputs: "Promise<string>"

whereas:

const wdioElement = await $('elem')
console.log(wdioElement.selector) // outputs: "elem"

So I am not sure if we can merge this into one single type since the chainable element is not resolved and accessing properties requires to use await. I can see that ChainablePromiseElement is confusing and I am in favor to consolidate this. Any suggestion how we can overcome the type difference?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, does this mean that you need to await the $ command when accessing any properties but not when you want to execute an action later or chain it with another $ or $$ directly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. You can do $('elem').$('elem').$('elem').$('elem') as our proxy command wrapper allows to chain these promises.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can extend the type to check if it has the elementId and if not you can assign it a Promise like type. I knowledge of Typescript is not where I want it to be so I might get a course on it to learn about more complex types 👍

Omit<WebdriverIO.Element, keyof ChainablePromiseBaseElement | keyof AsyncElementProto> {}

interface AsyncIterators<T> {
/**
* Unwrap the nth element of the element list.
Expand All @@ -79,30 +44,6 @@ interface AsyncIterators<T> {
reduce: <T, U>(callback: (accumulator: U, currentValue: WebdriverIO.Element, currentIndex: number, array: T[]) => U | Promise<U>, initialValue?: U) => Promise<U>;
}

export interface ChainablePromiseArray<T> extends Promise<T>, AsyncIterators<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<WebdriverIO.Element>

/**
* Amount of element fetched.
*/
length: Promise<number>
/**
* selector used to fetch this element, can be
* - undefined if element was created via `$({ 'element-6066-11e4-a52e-4f735466cecf': 'ELEMENT-1' })`
* - a string if `findElement` was used and a reference was found
* - or a function if element was found via e.g. `$(() => document.body)`
*/
selector: Promise<Selector>
/**
* parent of the element if fetched via `$(parent).$(child)`
*/
parent: Promise<WebdriverIO.Element | WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser>
/**
* allow to access a specific index of the element set
*/
[n: number]: ChainablePromiseElement<WebdriverIO.Element | undefined>
}

export type BrowserCommandsType = Omit<$BrowserCommands, keyof ChainablePrototype> & ChainablePrototype
export type ElementCommandsType = Omit<$ElementCommands, keyof ChainablePrototype> & ChainablePrototype

Expand Down
3 changes: 1 addition & 2 deletions packages/webdriverio/src/utils/actions/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import type { ElementReference } from '@wdio/protocols'
import type { BaseActionParams, KeyActionType } from './base.js'
import BaseAction from './base.js'
import type { ChainablePromiseElement } from '../../types.js'

export type ButtonNames = 'left' | 'middle' | 'right'
export type Button = 0 | 1 | 2
Expand Down Expand Up @@ -39,7 +38,7 @@ const MOVE_PARAM_DEFAULTS = {
x: 0,
y: 0,
duration: 100,
origin: ORIGIN_DEFAULT as (Origin | ElementReference | ChainablePromiseElement<WebdriverIO.Element> | WebdriverIO.Element)
origin: ORIGIN_DEFAULT as (Origin | ElementReference | Awaited<WebdriverIO.Element>)
}

type PointerActionParams = Partial<typeof PARAM_DEFAULTS> & Partial<PointerActionUpParams>
Expand Down
3 changes: 1 addition & 2 deletions packages/webdriverio/src/utils/actions/wheel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { BaseActionParams } from './base.js'
import BaseAction from './base.js'
import type { ChainablePromiseElement } from '../../types.js'

export interface ScrollParams {
/**
Expand All @@ -22,7 +21,7 @@ export interface ScrollParams {
/**
* element origin
*/
origin?: WebdriverIO.Element | ChainablePromiseElement<WebdriverIO.Element>
origin?: Awaited<WebdriverIO.Element>
/**
* duration ratio be the ratio of time delta and duration
*/
Expand Down