Skip to content

Commit 4563fc9

Browse files
committed
Final code review
1 parent 631612d commit 4563fc9

File tree

6 files changed

+110
-83
lines changed

6 files changed

+110
-83
lines changed

docs/MultiRemote.md

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Multi-remote support is in active development.
44

55
## Usage
66

7-
By default, multi-remote queries (e.g., `getTitle`) fetch data from all remotes, simplifying tests where browsers share behavior.
7+
By default, multi-remote matchers fetch data (e.g., `getTitle`) from all remotes, simplifying tests where browsers share the same behavior.
88

99
Use the typed global constants:
1010
```ts
@@ -37,6 +37,18 @@ export const config: WebdriverIO.MultiremoteConfig = {
3737
}
3838
```
3939

40+
And an `it` test like:
41+
```ts
42+
import { multiremotebrowser as multiRemoteBrowser } from '@wdio/globals'
43+
44+
it('should have title "My Site Title"', async function () {
45+
await multiRemoteBrowser.url('https://mysite.com')
46+
47+
// ... assertions
48+
})
49+
```
50+
51+
4052
## Single Expected Value
4153
To test all remotes against the same value, pass a single expected value.
4254
```ts
@@ -80,7 +92,7 @@ To assert all remotes with a default value, overriding specific ones:
8092
- Options (e.g., `StringOptions`) apply globally.
8193
- Alpha support is limited to the `toHaveTitle` browser matcher.
8294
- Element matchers are planned.
83-
- Assertions currently throw on the first error. Future updates will report thrown errors as failures and if all remotes are in error it will throw.
95+
- Assertions currently throw on the first error. Future updates will report thrown errors as failures, and will only throw if all remotes fail.
8496
- SoftAssertions, snapshot services and network matchers might come after.
8597

8698
## Alternatives
@@ -95,25 +107,49 @@ Mocha Parameterized Example
95107
describe('Multiremote test', async () => {
96108
multiRemoteBrowser.instances.forEach(function (instance) {
97109
describe(`Test ${instance}`, function () {
98-
it('should have title "The Internet"', async function () {
110+
it('should have title "My Site Title"', async function () {
99111
const browser = multiRemoteBrowser.getInstance(instance)
100112
await browser.url('https://mysite.com')
101113

102-
await expect(browser).toHaveTitle("The Internet")
114+
await expect(browser).toHaveTitle("My Site Title")
103115
})
104116
})
105117
})
106118
})
107119
```
108-
### Direct Instance Access
120+
### Direct Instance Access (TypeScript)
109121
By extending the WebdriverIO `namespace` in TypeScript (see [documentation](https://webdriver.io/docs/multiremote/#extending-typescript-types)), you can directly access each instance and use `expect` on them.
110122

111123
```ts
112124
it('should have title per browsers', async () => {
113125
await multiRemoteBrowser.url('https://mysite.com')
114126

115-
await expect(multiRemoteBrowser.myChromeBrowser).toHaveTitle('The Internet')
116-
await expect(multiRemoteBrowser.myFirefoxBrowser).toHaveTitle('The Internet')
127+
await expect(multiRemoteBrowser.myChromeBrowser).toHaveTitle('My Chrome Site Title')
128+
await expect(multiRemoteBrowser.myFirefoxBrowser).toHaveTitle('My Firefox Site Title')
117129
})
118130
```
119131

132+
Required configuration:
133+
134+
File `type.d.ts`
135+
```ts
136+
declare namespace WebdriverIO {
137+
interface MultiRemoteBrowser {
138+
myChromeBrowser: WebdriverIO.Browser
139+
myFirefoxBrowser: WebdriverIO.Browser
140+
}
141+
}
142+
```
143+
144+
In `tsconfig.json`
145+
```json
146+
{
147+
"compilerOptions": {
148+
...
149+
},
150+
"include": [
151+
...
152+
"type.d.ts"
153+
]
154+
}
155+
```

src/matchers/browser/toHaveTitle.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { compareText, waitUntilResult } from '../../utils.js'
1+
import { compareText, waitUntilResultSucceed } from '../../utils.js'
22
import { DEFAULT_OPTIONS } from '../../constants.js'
33
import type { MaybeArray } from '../../util/multiRemoteUtil.js'
4-
import { getInstancesWithExpected } from '../../util/multiRemoteUtil.js'
4+
import { mapExpectedValueWithInstances } from '../../util/multiRemoteUtil.js'
55
import { formatFailureMessage } from '../../util/formatMessage.js'
66

77
type ExpectedValueType = string | RegExp | WdioAsymmetricMatcher<string>
@@ -33,17 +33,17 @@ export async function toHaveTitle(
3333
options,
3434
})
3535

36-
const browsers = getInstancesWithExpected(browser, expectedValue)
36+
const browsersWithExpected = mapExpectedValueWithInstances(browser, expectedValue)
3737

38-
const conditions = Object.entries(browsers).map(([instance, { browser, expectedValue: expected }]) => async () => {
38+
const conditions = Object.entries(browsersWithExpected).map(([instanceName, { browser, expectedValue: expected }]) => async () => {
3939
const actual = await browser.getTitle()
4040

41-
const result = compareText(actual, expected as ExpectedValueType, options)
42-
result.instance = instance
41+
const result = compareText(actual, expected, options)
42+
result.instance = instanceName
4343
return result
4444
})
4545

46-
const conditionsResults = await waitUntilResult(
46+
const conditionsResults = await waitUntilResultSucceed(
4747
conditions,
4848
isNot,
4949
options,

src/util/multiRemoteUtil.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ export const toArray = <T>(value: T | T[] | MaybeArray<T>): T[] => (Array.isArra
44

55
export type MaybeArray<T> = T | T[]
66

7-
export function isArray<T>(value: unknown): value is T[] {
8-
return Array.isArray(value)
9-
}
10-
117
export const isMultiRemote = (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): browser is WebdriverIO.MultiRemoteBrowser => {
128
return (browser as WebdriverIO.MultiRemoteBrowser).isMultiremote === true
139
}
1410

15-
export const getInstancesWithExpected = <T>(browsers: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, expectedValues: T): Record<string, { browser: Browser; expectedValue: T; }> => {
11+
type BrowserWithExpected<T> = Record<string, {
12+
browser: Browser;
13+
expectedValue: T;
14+
}>
15+
16+
export const mapExpectedValueWithInstances = <T>(browsers: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, expectedValues: T | MaybeArray<T>): BrowserWithExpected<T> => {
1617
if (isMultiRemote(browsers)) {
1718
if (Array.isArray(expectedValues)) {
1819
if (expectedValues.length !== browsers.instances.length) {
@@ -21,15 +22,15 @@ export const getInstancesWithExpected = <T>(browsers: WebdriverIO.Browser | Webd
2122
}
2223
// TODO multi-remote support: add support for object like { default: 'title', browserA: 'titleA', browserB: 'titleB' } later
2324

24-
const browsersWithExpected = browsers.instances.reduce((acc, instance, index) => {
25+
const browsersWithExpected = browsers.instances.reduce((acc: BrowserWithExpected<T>, instance, index) => {
2526
const browser = browsers.getInstance(instance)
26-
const expectedValue = Array.isArray(expectedValues) ? expectedValues[index] : expectedValues
27+
const expectedValue: T = Array.isArray(expectedValues) ? expectedValues[index] : expectedValues
2728
acc[instance] = { browser, expectedValue }
2829
return acc
29-
}, {} as Record<string, { browser: WebdriverIO.Browser, expectedValue: T }>)
30+
}, {})
3031
return browsersWithExpected
3132
}
3233

3334
// TODO multi-remote support: using default could clash if someone use name default, to review later
34-
return { default: { browser: browsers, expectedValue: expectedValues } }
35+
return { default: { browser: browsers, expectedValue: expectedValues as T } }
3536
}

src/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ function isStringContainingMatcher(expected: unknown): expected is WdioAsymmetri
4949
* @param isNot https://jestjs.io/docs/expect#thisisnot
5050
* @param options wait, interval, etc
5151
*/
52-
const waitUntilResult = async <A = unknown, E = unknown>(
52+
const waitUntilResultSucceed = async <A = unknown, E = unknown>(
5353
condition: (() => Promise<CompareResult<A, E> | CompareResult<A, E>[]>) | (() => Promise<CompareResult<A, E>>)[],
5454
isNot = false,
5555
{ wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {},
5656
): Promise<{ pass: boolean, results: CompareResult<A, E>[] }> => {
5757
/**
5858
* Using array algorithm to handle both single and multiple conditions uniformly
59-
* Technically, this is an o(n3) operation, but practically, we process either a single promise with Array or an Array of promises. Review later if we can simplify and only have an array of promises
59+
* Technically, this is an o(n3) operation, but practically, we process either a single promise returning Array or an Array of promises. Review later if we can simplify and only have an array of promises
6060
*/
6161
const conditions = toArray(condition)
6262
// single attempt
@@ -257,7 +257,7 @@ export const compareText = (
257257
expectedValue.toString() === 'StringContaining'
258258
? expect.stringContaining(expectedValue.sample?.toString().toLowerCase())
259259
: expect.not.stringContaining(expectedValue.sample?.toString().toLowerCase())
260-
) as WdioAsymmetricMatcher<string>
260+
) satisfies Partial<WdioAsymmetricMatcher<string>> as WdioAsymmetricMatcher<string>
261261
}
262262
}
263263

@@ -469,7 +469,7 @@ function aliasFn(
469469

470470
export {
471471
aliasFn, compareNumbers, enhanceError, executeCommand,
472-
executeCommandBe, numberError, waitUntil, waitUntilResult, wrapExpectedWithArray
472+
executeCommandBe, numberError, waitUntil, waitUntilResultSucceed, wrapExpectedWithArray
473473
}
474474

475475
function replaceActual(

test/util/multiRemoteUtil.test.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest'
2-
import { toArray, isArray, isMultiRemote, getInstancesWithExpected } from '../../src/util/multiRemoteUtil.js'
2+
import { toArray, isMultiRemote, mapExpectedValueWithInstances } from '../../src/util/multiRemoteUtil.js'
33

44
describe('multiRemoteUtil', () => {
55
describe(toArray, () => {
@@ -21,16 +21,6 @@ describe('multiRemoteUtil', () => {
2121
})
2222
})
2323

24-
describe(isArray, () => {
25-
it('should return true if input is array', () => {
26-
expect(isArray([1, 2])).toBe(true)
27-
})
28-
29-
it('should return false if input is not array', () => {
30-
expect(isArray(1)).toBe(false)
31-
})
32-
})
33-
3424
describe(isMultiRemote, () => {
3525
it('should return true if browser is multi-remote', () => {
3626
const browser = { isMultiremote: true } satisfies Partial<WebdriverIO.MultiRemoteBrowser> as WebdriverIO.MultiRemoteBrowser
@@ -48,11 +38,11 @@ describe('multiRemoteUtil', () => {
4838
})
4939
})
5040

51-
describe(getInstancesWithExpected, () => {
41+
describe(mapExpectedValueWithInstances, () => {
5242
it('should return default instance for single browser', () => {
5343
const browser = { isMultiremote: false } satisfies Partial<WebdriverIO.Browser> as WebdriverIO.Browser
5444
const expected = 'expected'
55-
const result = getInstancesWithExpected(browser, expected)
45+
const result = mapExpectedValueWithInstances(browser, expected)
5646
expect(result).toEqual({
5747
default: {
5848
browser,
@@ -78,7 +68,7 @@ describe('multiRemoteUtil', () => {
7868

7969
it('should return instances for multi-remote browser with single expected value', () => {
8070
const expected = 'expected'
81-
const result = getInstancesWithExpected(browser, expected)
71+
const result = mapExpectedValueWithInstances(browser, expected)
8272

8373
expect(result).toEqual({
8474
browserA: {
@@ -96,7 +86,7 @@ describe('multiRemoteUtil', () => {
9686

9787
it('should return instances for multi-remote browser with array of expected values', () => {
9888
const expected = ['expectedA', 'expectedB']
99-
const result = getInstancesWithExpected(browser, expected)
89+
const result = mapExpectedValueWithInstances(browser, expected)
10090

10191
expect(result).toEqual({
10292
browserA: {
@@ -112,7 +102,7 @@ describe('multiRemoteUtil', () => {
112102

113103
it('should throw error if expected values length does not match instances length', () => {
114104
const expected = ['expectedA']
115-
expect(() => getInstancesWithExpected(browser, expected)).toThrow('Expected values length (1) does not match number of browser instances (2) in multi-remote setup.')
105+
expect(() => mapExpectedValueWithInstances(browser, expected)).toThrow('Expected values length (1) does not match number of browser instances (2) in multi-remote setup.')
116106
})
117107
})
118108
})

0 commit comments

Comments
 (0)