Skip to content

Commit 4583e11

Browse files
build: fix eslint errors after kcd-scripts update
Most eslint warnings are related to the @typescript-eslint/no-explicit-any rule being switched on. It is possible to replace most uses of any with specific types, however there are two main areas where that is not possible: - The stub WebdriverIO types are intentionally loose to support multiple versions of WebdriverIO as well as differences between sync and async mode types. - The setupBrowser function does not currently have any way to know which query it is setting up in each iteration of the forEach, resulting in issues when typing the resulting query. This can't be resolved without a refactor of how the queries are set up. Likely each variant of query will need their own version of createQuery, e.g. createFindAllByQuery, createFindByQuery etc. There are additional errors resulting from the parserOptions.project property being set in the base eslint.js file. It looks like kcd-scripts assumes that there is a single tsconfig, whereas we need multiple tsconfigs to support the different WebdriverIO modes in our async and sync tests. This is fixed by overriding the parserOptions.project option to be a glob that matches all our tsconfigs.
1 parent d2cc3c3 commit 4583e11

File tree

6 files changed

+171
-83
lines changed

6 files changed

+171
-83
lines changed

.eslintrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
"./node_modules/kcd-scripts/eslint.js",
55
"plugin:import/typescript"
66
],
7+
"parserOptions": {
8+
"ecmaVersion": 2018,
9+
"sourceType": "module",
10+
"project": "*/**/tsconfig.json"
11+
},
712
"plugins": ["@typescript-eslint"],
13+
"ignorePatterns": "wdio.conf.js",
814
"rules": {
915
"babel/new-cap": "off",
1016
"func-names": "off",

src/index.ts

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
/* eslint-disable babel/no-invalid-this, no-eval */
1+
/* eslint-disable no-eval, @babel/new-cap */
22

33
import path from 'path'
44
import fs from 'fs'
5-
import {queries as baseQueries} from '@testing-library/dom'
5+
import {
6+
Matcher,
7+
MatcherOptions,
8+
queries as baseQueries,
9+
waitForOptions as WaitForOptions,
10+
} from '@testing-library/dom'
611
import 'simmerjs'
712

13+
import {BrowserBase, ElementBase} from './wdio-types'
814
import {
9-
BrowserBase,
15+
QueryArg,
1016
Config,
11-
ElementBase,
1217
QueryName,
1318
WebdriverIOQueries,
19+
ObjectQueryArg,
20+
SerializedObject,
21+
SerializedArg,
1422
} from './types'
1523

1624
declare global {
@@ -45,9 +53,9 @@ async function injectDOMTestingLibrary(container: ElementBase) {
4553
})
4654

4755
if (shouldInject.domTestingLibrary) {
48-
await container.execute(function (library) {
56+
await container.execute(function (library: string) {
4957
// add DOM Testing Library to page as a script tag to support Firefox
50-
if (navigator.userAgent.indexOf('Firefox') !== -1) {
58+
if (navigator.userAgent.includes('Firefox')) {
5159
const script = document.createElement('script')
5260
script.innerHTML = library
5361
return document.head.append(script)
@@ -62,74 +70,83 @@ async function injectDOMTestingLibrary(container: ElementBase) {
6270
await container.execute(SIMMERJS)
6371
}
6472

65-
if (_config) {
66-
await container.execute(function (config: Config) {
67-
window.TestingLibraryDom.configure(config)
68-
}, _config)
69-
}
73+
await container.execute(function (config: Config) {
74+
window.TestingLibraryDom.configure(config)
75+
}, _config)
7076
}
7177

72-
function serializeObject(object: Object): Object {
78+
function serializeObject(object: ObjectQueryArg): SerializedObject {
7379
return Object.entries(object)
74-
.map(([key, value]) => [key, serializeArg(value)])
75-
.reduce((acc, [key, value]) => ({...acc, [key]: value}), {})
80+
.map<[string, SerializedArg]>(([key, value]: [string, QueryArg]) => [
81+
key,
82+
serializeArg(value),
83+
])
84+
.reduce((acc, [key, value]) => ({...acc, [key]: value}), {
85+
serialized: 'object',
86+
})
7687
}
7788

78-
function serializeArg(arg: any) {
89+
function serializeArg(arg: QueryArg): SerializedArg {
7990
if (arg instanceof RegExp) {
80-
return {RegExp: arg.toString()}
91+
return {serialized: 'RegExp', RegExp: arg.toString()}
8192
}
8293
if (typeof arg === 'undefined') {
83-
return {Undefined: true}
94+
return {serialized: 'Undefined', Undefined: true}
8495
}
8596
if (arg && typeof arg === 'object') {
8697
return serializeObject(arg)
8798
}
8899
return arg
89100
}
90101

102+
type SerializedQueryResult =
103+
| {selector: string | false; element: HTMLElement}[]
104+
| string
105+
| {selector: string | false; element: HTMLElement}
106+
| null
107+
91108
function executeQuery(
92109
query: QueryName,
93110
container: HTMLElement,
94-
...args: any[]
111+
...args: SerializedArg[]
95112
) {
96-
const done = args.pop() as (result: any) => void
113+
const done = args.pop() as unknown as (result: SerializedQueryResult) => void
97114

98-
function deserializeObject(object: object): object {
115+
function deserializeObject(object: SerializedObject) {
99116
return Object.entries(object)
100-
.map(([key, value]) => [key, deserializeArg(value)])
117+
.map<[string, QueryArg]>(([key, value]) => [key, deserializeArg(value)])
101118
.reduce((acc, [key, value]) => ({...acc, [key]: value}), {})
102119
}
103120

104-
function deserializeArg(arg: any) {
105-
if (arg && arg.RegExp) {
121+
function deserializeArg(arg: SerializedArg): QueryArg {
122+
if (typeof arg === 'object' && arg.serialized === 'RegExp') {
106123
return eval(arg.RegExp)
107124
}
108-
if (arg && arg.Undefined) {
125+
if (typeof arg === 'object' && arg.serialized === 'Undefined') {
109126
return undefined
110127
}
111-
if (arg && typeof arg === 'object') {
128+
if (typeof arg === 'object') {
112129
return deserializeObject(arg)
113130
}
114131
return arg
115132
}
116133

117134
const [matcher, options, waitForOptions] = args.map(deserializeArg)
118135

119-
;(async () => {
120-
let result: undefined | null | HTMLElement | HTMLElement[]
136+
void (async () => {
137+
let result: ReturnType<typeof window.TestingLibraryDom[typeof query]> = null
121138
try {
122139
// Override RegExp to fix 'matcher instanceof RegExp' check on Firefox
123140
window.RegExp = RegExp
124141

125142
result = await window.TestingLibraryDom[query](
126143
container,
127-
matcher,
128-
options,
129-
waitForOptions,
144+
matcher as Matcher,
145+
options as MatcherOptions,
146+
waitForOptions as WaitForOptions,
130147
)
131-
} catch (e) {
132-
done(e.message)
148+
} catch (e: unknown) {
149+
return done((e as Error).message)
133150
}
134151

135152
if (!result) {
@@ -160,10 +177,10 @@ Element. There are valid WebElement JSONs that exclude the key but can be turned
160177
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
161178
aren't generated by @wdio/cli.
162179
*/
163-
function createElement(
180+
async function createElement(
164181
container: ElementBase,
165-
result: {selector: string | false; element: any},
166-
) {
182+
result: {selector: string | false; element: object},
183+
): Promise<WebdriverIO.Element> {
167184
// use selector if possible so that element can be refetched
168185
if (result.selector) {
169186
return container.$(result.selector)
@@ -176,11 +193,12 @@ function createElement(
176193
})
177194
}
178195

179-
function createQuery(container: ElementBase, queryName: string) {
180-
return async (...args: any[]) => {
196+
function createQuery(container: ElementBase, queryName: QueryName) {
197+
return async (...args: QueryArg[]) => {
181198
await injectDOMTestingLibrary(container)
182199

183-
const result = await container.executeAsync(
200+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
201+
const result: SerializedQueryResult = await container.executeAsync(
184202
executeQuery,
185203
queryName,
186204
container,
@@ -204,7 +222,7 @@ function createQuery(container: ElementBase, queryName: string) {
204222
}
205223

206224
function within(element: ElementBase) {
207-
return Object.keys(baseQueries).reduce(
225+
return (Object.keys(baseQueries) as QueryName[]).reduce(
208226
(queries, queryName) => ({
209227
...queries,
210228
[queryName]: createQuery(element, queryName),
@@ -213,22 +231,29 @@ function within(element: ElementBase) {
213231
) as WebdriverIOQueries
214232
}
215233

216-
function setupBrowser(browser: BrowserBase) {
217-
const queries: {[key: string]: any} = {}
234+
/*
235+
eslint-disable
236+
@typescript-eslint/no-explicit-any,
237+
@typescript-eslint/no-unsafe-argument
238+
*/
239+
function setupBrowser(browser: BrowserBase): WebdriverIOQueries {
240+
const queries: {[key: string]: WebdriverIOQueries[QueryName]} = {}
218241

219242
Object.keys(baseQueries).forEach((key) => {
220-
const queryName = key as keyof typeof baseQueries
243+
const queryName = key as QueryName
221244

222-
const query = async (...args: any[]) => {
245+
const query = async (
246+
...args: Parameters<WebdriverIOQueries[QueryName]>
247+
) => {
223248
const body = await browser.$('body')
224-
return within(body)[queryName](...args)
249+
return within(body)[queryName](...(args as any[]))
225250
}
226251

227252
// add query to response queries
228-
queries[queryName] = query
253+
queries[queryName] = query as WebdriverIOQueries[QueryName]
229254

230255
// add query to BrowserObject
231-
browser.addCommand(queryName, query)
256+
browser.addCommand(queryName, query as WebdriverIOQueries[QueryName])
232257

233258
// add query to Elements
234259
browser.addCommand(
@@ -242,6 +267,11 @@ function setupBrowser(browser: BrowserBase) {
242267

243268
return queries as WebdriverIOQueries
244269
}
270+
/*
271+
eslint-enable
272+
@typescript-eslint/no-explicit-any,
273+
@typescript-eslint/no-unsafe-argument
274+
*/
245275

246276
function configure(config: Partial<Config>) {
247277
_config = config

src/types.ts

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,17 @@ import {
22
Config as BaseConfig,
33
BoundFunction as BoundFunctionBase,
44
queries,
5+
waitForOptions,
6+
SelectorMatcherOptions,
7+
MatcherOptions,
58
} from '@testing-library/dom'
69

7-
declare global {
8-
namespace WebdriverIO {
9-
interface Element extends ElementBase {}
10-
}
11-
}
12-
13-
export type ElementBase = {
14-
$(
15-
selector: string | object,
16-
): WebdriverIO.Element | Promise<WebdriverIO.Element>
17-
18-
execute<T>(
19-
script: string | ((...args: any[]) => T),
20-
...args: any[]
21-
): Promise<T>
22-
23-
execute<T>(script: string | ((...args: any[]) => T), ...args: any[]): T
24-
25-
executeAsync(script: string | ((...args: any[]) => void), ...args: any[]): any
26-
}
27-
28-
export type BrowserBase = {
29-
$(
30-
selector: string | object,
31-
): WebdriverIO.Element | Promise<WebdriverIO.Element>
32-
33-
addCommand<T extends boolean>(
34-
queryName: string,
35-
commandFn: (
36-
this: T extends true ? ElementBase : BrowserBase,
37-
...args: any[]
38-
) => void,
39-
isElementCommand?: T,
40-
): any
41-
}
42-
4310
export type Config = Pick<
4411
BaseConfig,
45-
| 'testIdAttribute'
4612
| 'asyncUtilTimeout'
4713
| 'computedStyleSupportsPseudoElements'
4814
| 'defaultHidden'
15+
| 'testIdAttribute'
4916
| 'throwSuggestions'
5017
>
5118

@@ -83,3 +50,30 @@ export type WebdriverIOQueriesSync = WebdriverIOBoundFunctionsSync<
8350
>
8451

8552
export type QueryName = keyof typeof queries
53+
54+
export type ObjectQueryArg =
55+
| MatcherOptions
56+
| queries.ByRoleOptions
57+
| SelectorMatcherOptions
58+
| waitForOptions
59+
60+
export type QueryArg =
61+
| ObjectQueryArg
62+
| RegExp
63+
| number
64+
| string
65+
| undefined
66+
67+
export type SerializedObject = {
68+
serialized: 'object'
69+
[key: string]: SerializedArg
70+
}
71+
export type SerializedRegExp = {serialized: 'RegExp'; RegExp: string}
72+
export type SerializedUndefined = {serialized: 'Undefined'; Undefined: true}
73+
74+
export type SerializedArg =
75+
| SerializedObject
76+
| SerializedRegExp
77+
| SerializedUndefined
78+
| number
79+
| string

src/wdio-types.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Types related to WebdriverIO are intentionally loose in order to support wdio
3+
version 6 and 7 at the same time. Disable eslint rules that prevent that.
4+
*/
5+
6+
/*
7+
eslint-disable @typescript-eslint/no-explicit-any,
8+
@typescript-eslint/no-namespace,
9+
@typescript-eslint/no-empty-interface
10+
*/
11+
12+
declare global {
13+
namespace WebdriverIO {
14+
interface Element extends ElementBase {}
15+
}
16+
}
17+
18+
export type ElementBase = {
19+
$(
20+
selector: object | string,
21+
): Promise<WebdriverIO.Element> | WebdriverIO.Element
22+
23+
execute<T>(
24+
script: string | ((...args: any[]) => T),
25+
...args: any[]
26+
): Promise<T>
27+
28+
execute<T>(script: string | ((...args: any[]) => T), ...args: any[]): T
29+
30+
executeAsync(script: string | ((...args: any[]) => void), ...args: any[]): any
31+
}
32+
33+
export type BrowserBase = {
34+
$(
35+
selector: object | string,
36+
): Promise<WebdriverIO.Element> | WebdriverIO.Element
37+
38+
addCommand<T extends boolean>(
39+
queryName: string,
40+
commandFn: (
41+
this: T extends true ? ElementBase : BrowserBase,
42+
...args: any[]
43+
) => void,
44+
isElementCommand?: T,
45+
): any
46+
}

test/async/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
eslint-disable
3+
@typescript-eslint/no-namespace,
4+
@typescript-eslint/no-empty-interface
5+
*/
6+
17
import {WebdriverIOQueries} from '../../src'
28

39
declare global {

0 commit comments

Comments
 (0)