Skip to content

Commit fbe3663

Browse files
fix: typescript sync mode compilation
Importing the webdriverio package directly also declares async types for browser APIs - this was missed due to sync mode tests using javascript to avoid conflicts with the global declarations of command types. To fix compilation of sync mode projects the types for the methods used by the lib are manually defined instead of using WebdriverIO types directly. In order to ensure the WebdriverIO.Element type the queries return is correct a WebdriverIO namespace with an empty Element interface is declared; the queries return this empty interface so that the end user can hydrate the interface with the correct types for the mode of WebdriverIO they are using. BREAKING CHANGE: users will now need to include WebdriverIO types in their tsconfig.json in order to hydrate the WebdriverIO.Element type. In order to verify the lib compiles for both async and sync mode projects the test folder has been reorganised into two sets of tests: those that use async mode and those that use sync mode. To achieve this a tsconfig is included in each directory that includes the corresponding WebdriverIO types for their mode. In order to verify each test mode compiles correctly `typecheck` npm scripts have been added and are used in the validate script.
1 parent 67a14e0 commit fbe3663

17 files changed

+152
-69
lines changed

.eslintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"testing-library/no-await-sync-query": "off",
1414
"testing-library/no-dom-import": "off",
1515
"testing-library/prefer-screen-queries": "off",
16+
"no-undef": "off",
17+
"no-use-before-define": "off",
1618
"no-unused-vars": "off",
1719
"@typescript-eslint/no-unused-vars": ["error"]
1820
},

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
"build": "tsc -p tsconfig.build.json",
1010
"lint": "kcd-scripts lint",
1111
"test:unit": "kcd-scripts test --no-watch --config=jest.config.js",
12-
"validate": "kcd-scripts validate build,lint,test",
12+
"validate": "kcd-scripts validate build,lint,test,typecheck",
1313
"test": "wdio wdio.conf.js",
14-
"semantic-release": "semantic-release"
14+
"semantic-release": "semantic-release",
15+
"typecheck:async": "tsc -p ./test/async/tsconfig.json",
16+
"typecheck:sync": "tsc -p ./test/sync/tsconfig.json",
17+
"typecheck:build": "npm run build -- --noEmit",
18+
"typecheck": "npm-run-all typecheck:build typecheck:**"
1519
},
1620
"files": [
1721
"dist"
@@ -38,6 +42,7 @@
3842
"chromedriver": "^89.0.0",
3943
"eslint": "^7.6.0",
4044
"kcd-scripts": "^5.0.0",
45+
"npm-run-all": "^4.1.5",
4146
"semantic-release": "^17.0.2",
4247
"ts-node": "^9.1.1",
4348
"typescript": "^4.1.3",

src/index.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import path from 'path'
44
import fs from 'fs'
55
import {queries as baseQueries} from '@testing-library/dom'
6-
import {Element, BrowserObject, MultiRemoteBrowserObject} from 'webdriverio'
76

8-
import {Config, QueryName, WebdriverIOQueries} from './types'
7+
import {
8+
BrowserBase,
9+
Config,
10+
ElementBase,
11+
QueryName,
12+
WebdriverIOQueries,
13+
} from './types'
914

1015
declare global {
1116
interface Window {
@@ -26,7 +31,7 @@ const DOM_TESTING_LIBRARY_UMD = fs
2631

2732
let _config: Partial<Config>
2833

29-
async function injectDOMTestingLibrary(container: Element) {
34+
async function injectDOMTestingLibrary(container: ElementBase) {
3035
const shouldInject = await container.execute(function () {
3136
return !window.TestingLibraryDom
3237
})
@@ -108,18 +113,18 @@ Element. There are valid WebElement JSONs that exclude the key but can be turned
108113
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
109114
aren't generated by @wdio/cli.
110115
*/
111-
function createElement(container: Element, elementValue: any) {
116+
function createElement(container: ElementBase, elementValue: any) {
112117
return container.$({
113118
'element-6066-11e4-a52e-4f735466cecf': '',
114119
...elementValue,
115120
})
116121
}
117122

118-
function createQuery(element: Element, queryName: string) {
123+
function createQuery(element: ElementBase, queryName: string) {
119124
return async (...args: any[]) => {
120125
await injectDOMTestingLibrary(element)
121126

122-
const result = await element.executeAsync<any[], any[]>(executeQuery, [
127+
const result = await element.executeAsync(executeQuery, [
123128
queryName,
124129
element,
125130
...args.map(serializeArg),
@@ -141,7 +146,7 @@ function createQuery(element: Element, queryName: string) {
141146
}
142147
}
143148

144-
function within(element: Element) {
149+
function within(element: ElementBase) {
145150
return Object.keys(baseQueries).reduce(
146151
(queries, queryName) => ({
147152
...queries,
@@ -151,7 +156,7 @@ function within(element: Element) {
151156
) as WebdriverIOQueries
152157
}
153158

154-
function setupBrowser(browser: BrowserObject | MultiRemoteBrowserObject) {
159+
function setupBrowser(browser: BrowserBase) {
155160
const queries: {[key: string]: any} = {}
156161

157162
Object.keys(baseQueries).forEach((key) => {
@@ -171,9 +176,8 @@ function setupBrowser(browser: BrowserObject | MultiRemoteBrowserObject) {
171176
// add query to Elements
172177
browser.addCommand(
173178
queryName,
174-
function (...args: any[]) {
175-
const element = this as Element
176-
return within(element)[queryName](...args)
179+
function (this, ...args) {
180+
return within(this)[queryName](...args)
177181
},
178182
true,
179183
)

src/types.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,45 @@ import {
33
BoundFunction as BoundFunctionBase,
44
queries,
55
} from '@testing-library/dom'
6-
import {Element} from 'webdriverio'
6+
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>(
24+
script: string | ((...args: any[]) => T),
25+
...args: any[]
26+
): T
27+
28+
executeAsync(script: string | ((...args: any[]) => void), ...args: any[]): any
29+
}
30+
31+
export type BrowserBase = {
32+
$(
33+
selector: string | object,
34+
): WebdriverIO.Element | Promise<WebdriverIO.Element>
35+
36+
addCommand<T extends boolean>(
37+
queryName: string,
38+
commandFn: (
39+
this: T extends true ? ElementBase : BrowserBase,
40+
...args: any[]
41+
) => void,
42+
isElementCommand?: T,
43+
): any
44+
}
745

846
export type Config = Pick<
947
BaseConfig,
@@ -15,13 +53,13 @@ export type Config = Pick<
1553
>
1654

1755
export type WebdriverIOQueryReturnType<T> = T extends Promise<HTMLElement>
18-
? Element
56+
? WebdriverIO.Element
1957
: T extends HTMLElement
20-
? Element
58+
? WebdriverIO.Element
2159
: T extends Promise<HTMLElement[]>
22-
? Element[]
60+
? WebdriverIO.Element[]
2361
: T extends HTMLElement[]
24-
? Element[]
62+
? WebdriverIO.Element[]
2563
: T extends null
2664
? null
2765
: never

test/configure.e2e.ts renamed to test/async/configure.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {setupBrowser, configure} from '../src'
1+
import {setupBrowser, configure} from '../../src'
22

33
describe('configure', () => {
44
afterEach(() => {

test/queries.e2e.ts renamed to test/async/queries.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {setupBrowser} from '../src'
1+
import {setupBrowser} from '../../src'
22

33
describe('queries', () => {
44
it('queryBy resolves with matching element', async () => {

test/setupBrowser.e2e.ts renamed to test/async/setupBrowser.e2e.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
import path from 'path'
22
import {queries as baseQueries} from '@testing-library/dom'
33

4-
import {setupBrowser} from '../src'
5-
import {WebdriverIOQueries} from '../src/types'
6-
7-
declare global {
8-
namespace WebdriverIO {
9-
interface Browser extends WebdriverIOQueries {}
10-
interface Element extends WebdriverIOQueries {}
11-
}
12-
}
4+
import {setupBrowser} from '../../src'
135

146
describe('setupBrowser', () => {
157
it('resolves with all queries', () => {
@@ -49,7 +41,7 @@ describe('setupBrowser', () => {
4941

5042
await browser.reloadSession()
5143
await browser.url(
52-
`file:///${path.join(__dirname, '../test-app/index.html')}`,
44+
`file:///${path.join(__dirname, '../../test-app/index.html')}`,
5345
)
5446

5547
expect(await getByText('Page Heading')).toBeDefined()

test/async/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"types": ["node", "webdriverio", "@wdio/mocha-framework"],
5+
"baseUrl": "."
6+
},
7+
"exclude": [],
8+
"include": ["**/*.ts"]
9+
}

test/async/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {WebdriverIOQueries} from '../../src'
2+
3+
declare global {
4+
namespace WebdriverIO {
5+
interface Browser extends WebdriverIOQueries {}
6+
interface Element extends WebdriverIOQueries {}
7+
}
8+
}

test/within.e2e.ts renamed to test/async/within.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {within, setupBrowser} from '../src';
1+
import {within, setupBrowser} from '../../src'
22

33
describe('within', () => {
44
it('scopes queries to element', async () => {

0 commit comments

Comments
 (0)