Skip to content

Commit ca9a523

Browse files
chore(wait): add types and fix issues flagged by types (#32332)
* chore(wait): add types and fix issues flagged by types * refactor: create shallow copy of aliases --------- Co-authored-by: Bill Glesias <[email protected]>
1 parent 97334ba commit ca9a523

File tree

5 files changed

+85
-56
lines changed

5 files changed

+85
-56
lines changed

packages/driver/cypress/e2e/commands/waiting.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('src/cy/commands/waiting', () => {
4747
cy
4848
.wait(50)
4949
.then(() => {
50-
expect(timeout).to.be.calledWith(50, true, 'wait')
50+
expect(timeout).to.be.calledWith(50, true)
5151
})
5252
})
5353
})

packages/driver/src/cy/aliases.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import _ from 'lodash'
22
import type { $Cy } from '../cypress/cy'
33

44
import $errUtils from '../cypress/error_utils'
5+
import type { SubjectChain } from '../cypress/state'
56

67
export const aliasRe = /^@.+/
78

@@ -12,13 +13,13 @@ const requestXhrRe = /\.request$/
1213

1314
const reserved = ['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect']
1415

15-
export const aliasDisplayName = (name) => {
16+
export const aliasDisplayName = (name: string) => {
1617
return name.replace(aliasDisplayRe, '')
1718
}
1819

1920
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
2021
export const create = (cy: $Cy) => ({
21-
addAlias (ctx, aliasObj) {
22+
addAlias (ctx: Mocha.Context, aliasObj: { alias: string, subjectChain: SubjectChain }) {
2223
const { alias } = aliasObj
2324

2425
const aliases = cy.state('aliases') || {}

packages/driver/src/cy/commands/waiting.ts

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,45 @@ import { waitForRoute } from '../net-stubbing/wait-for-route'
44
import { isDynamicAliasingPossible } from '../net-stubbing/aliasing'
55
import ordinal from 'ordinal'
66

7-
import $errUtils from '../../cypress/error_utils'
7+
import $errUtils, { type CypressError, type InternalCypressError } from '../../cypress/error_utils'
8+
import type { $Cy } from '../../cypress/cy'
9+
import type { StateFunc } from '../../cypress/state'
10+
import type { Log } from '../../cypress/log'
11+
12+
type waitOptions = {
13+
_log?: Cypress.Log
14+
_runnableTimeout?: number
15+
error?: CypressError | InternalCypressError
16+
isCrossOriginSpecBridge?: boolean
17+
log: boolean
18+
requestTimeout?: number
19+
responseTimeout?: number
20+
timeout: number
21+
type?: 'request' | 'response'
22+
}
823

9-
const getNumRequests = (state, alias) => {
10-
const requests = state('aliasRequests') || {}
24+
const getNumRequests = (state: StateFunc, alias: string) => {
25+
const requests = state('aliasRequests') ?? {}
1126

12-
requests[alias] = requests[alias] || 0
27+
requests[alias] = requests[alias] ?? 0
1328

1429
const index = requests[alias]
1530

1631
requests[alias] += 1
1732

1833
state('aliasRequests', requests)
1934

20-
return [index, ordinal(requests[alias])]
35+
return [index, ordinal(requests[alias])] as const
2136
}
2237

23-
const throwErr = (arg) => {
38+
const throwErr = (arg: string) => {
2439
$errUtils.throwErrByPath('wait.invalid_1st_arg', { args: { arg } })
2540
}
2641

27-
type Alias = {
28-
name: string
29-
cardinal: number
30-
ordinal: number
31-
}
32-
33-
export default (Commands, Cypress, cy, state) => {
34-
const waitNumber = (subject, ms, options) => {
42+
export default (Commands: Cypress.Commands, Cypress: Cypress.Cypress, cy: $Cy, state: StateFunc) => {
43+
const waitNumber = (subject: unknown, ms: number, options: waitOptions) => {
3544
// increase the timeout by the delta
36-
cy.timeout(ms, true, 'wait')
45+
cy.timeout(ms, true)
3746

3847
options._log = Cypress.log({
3948
hidden: options.log === false,
@@ -51,7 +60,7 @@ export default (Commands, Cypress, cy, state) => {
5160
.return(subject)
5261
}
5362

54-
const waitString = (subject, str, options) => {
63+
const waitString = async (str: string | string[], options: waitOptions) => {
5564
// if this came from the spec bridge, we need to set a few additional properties to ensure the log displays correctly
5665
// otherwise, these props will be pulled from the current command which will be cy.origin on the primary
5766
const log = options._log = Cypress.log({
@@ -69,7 +78,13 @@ export default (Commands, Cypress, cy, state) => {
6978
})
7079
}
7180

72-
const checkForXhr = async function (alias, type, index, num, options) {
81+
const checkForXhr = async function (
82+
alias: string,
83+
type: 'request'|'response',
84+
index: number,
85+
num: string,
86+
options: waitOptions,
87+
) {
7388
options.error = $errUtils.errByPath('wait.timed_out', {
7489
timeout: options.timeout,
7590
alias,
@@ -98,15 +113,16 @@ export default (Commands, Cypress, cy, state) => {
98113
return xhr
99114
}
100115

101-
const args: [any, any, any, any, any] = [alias, type, index, num, options]
102-
103116
return cy.retry(() => {
104-
return checkForXhr.apply(window, args)
105-
}, options)
117+
return checkForXhr.apply(window, [alias, type, index, num, options])
118+
},
119+
// TODO: What should `_log`'s type be?
120+
// @ts-expect-error - Incompatible types.
121+
options)
106122
}
107123

108-
const waitForXhr = function (str, options) {
109-
let specifier
124+
const waitForXhr = async function (str: string, options: Omit<waitOptions, 'error'>) {
125+
let specifier: string | null | undefined
110126

111127
// we always want to strip everything after the last '.'
112128
// since we support alias property 'request'
@@ -126,7 +142,7 @@ export default (Commands, Cypress, cy, state) => {
126142
}
127143
}
128144

129-
let aliasObj
145+
let aliasObj: { alias: string, command?: unknown }
130146

131147
try {
132148
aliasObj = cy.getAlias(str, 'wait', log)
@@ -161,8 +177,8 @@ export default (Commands, Cypress, cy, state) => {
161177
// build up an array of referencesAlias
162178
// because wait can reference an array of aliases
163179
if (log) {
164-
const referencesAlias = log.get('referencesAlias') || []
165-
const aliases: Array<Alias> = [].concat(referencesAlias)
180+
const referencesAlias = log.get('referencesAlias') ?? []
181+
const aliases = [...referencesAlias]
166182

167183
if (str) {
168184
aliases.push({
@@ -182,13 +198,13 @@ export default (Commands, Cypress, cy, state) => {
182198
return commandsThatCreateNetworkIntercepts.includes(commandName)
183199
}
184200

185-
const findInterceptAlias = (alias) => {
186-
const routes = cy.state('routes') || {}
201+
const findInterceptAlias = (alias: string) => {
202+
const routes = cy.state('routes') ?? {}
187203

188204
return _.find(_.values(routes), { alias })
189205
}
190206

191-
const isInterceptAlias = (alias) => Boolean(findInterceptAlias(alias))
207+
const isInterceptAlias = (alias: string) => Boolean(findInterceptAlias(alias))
192208

193209
if (command && !isNetworkInterceptCommand(command)) {
194210
if (!isInterceptAlias(alias)) {
@@ -203,11 +219,15 @@ export default (Commands, Cypress, cy, state) => {
203219
// but slice out the error since we may set
204220
// the error related to a previous xhr
205221
const { timeout } = options
222+
// TODO: If `options.requestTimeout` and `options.responseTimeout` are
223+
// `0`, is this code going to work the way it was intended to?
206224
const requestTimeout = options.requestTimeout || timeout
207225
const responseTimeout = options.responseTimeout || timeout
208226

209227
const waitForRequest = () => {
210228
options = _.omit(options, '_runnableTimeout')
229+
// TODO: If `requestTimeout` is `0`, is this code going to work the way
230+
// it was intended to?
211231
options.timeout = requestTimeout || Cypress.config('requestTimeout')
212232

213233
if (log) {
@@ -219,6 +239,8 @@ export default (Commands, Cypress, cy, state) => {
219239

220240
const waitForResponse = () => {
221241
options = _.omit(options, '_runnableTimeout')
242+
// TODO: If `responseTimeout` is `0`, is this code going to work the way
243+
// it was intended to?
222244
options.timeout = responseTimeout || Cypress.config('responseTimeout')
223245

224246
if (log) {
@@ -237,8 +259,7 @@ export default (Commands, Cypress, cy, state) => {
237259
return waitForRequest().then(waitForResponse)
238260
}
239261

240-
return Promise
241-
.map([].concat(str), (str) => {
262+
return Promise.map(([] as string[]).concat(str), (str) => {
242263
// we may get back an xhr value instead
243264
// of a promise, so we have to wrap this
244265
// in another promise :-(
@@ -285,26 +306,32 @@ export default (Commands, Cypress, cy, state) => {
285306
})
286307
}
287308

288-
Cypress.primaryOriginCommunicator.on('wait:for:xhr', ({ args: [str, options] }, { origin }) => {
289-
options.isCrossOriginSpecBridge = true
290-
waitString(null, str, options).then((responses) => {
291-
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'wait:for:xhr:end', responses)
292-
}).catch((err) => {
293-
options._log?.error(err)
294-
err.hasSpecBridgeError = true
295-
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'wait:for:xhr:end', err)
296-
})
297-
})
309+
Cypress.primaryOriginCommunicator.on(
310+
'wait:for:xhr',
311+
(
312+
{ args: [str, options] }: { args: [string | string[], waitOptions] },
313+
{ origin },
314+
) => {
315+
options.isCrossOriginSpecBridge = true
316+
waitString(str, options).then((responses) => {
317+
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'wait:for:xhr:end', responses)
318+
}).catch((err) => {
319+
options._log?.error(err)
320+
err.hasSpecBridgeError = true
321+
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'wait:for:xhr:end', err)
322+
})
323+
},
324+
)
298325

299-
const delegateToPrimaryOrigin = ([_subject, str, options]) => {
326+
const delegateToPrimaryOrigin = (str: string | string[], options: waitOptions) => {
300327
return new Promise((resolve, reject) => {
301328
Cypress.specBridgeCommunicator.once('wait:for:xhr:end', (responsesOrErr) => {
302329
// determine if this is an error by checking if there is a spec bridge error
303330
if (responsesOrErr.hasSpecBridgeError) {
304331
delete responsesOrErr.hasSpecBridgeError
305332
if (options.log) {
306333
// skip this 'wait' log since it was already added through the primary
307-
Cypress.state('onBeforeLog', (log) => {
334+
Cypress.state('onBeforeLog', (log: Log) => {
308335
if (log.get('name') === 'wait') {
309336
// unbind this function so we don't impact any other logs
310337
cy.state('onBeforeLog', null)
@@ -328,7 +355,7 @@ export default (Commands, Cypress, cy, state) => {
328355
}
329356

330357
Commands.addAll({ prevSubject: 'optional' }, {
331-
wait (subject, msOrAlias, options: { log?: boolean } = {}) {
358+
wait (subject: unknown, msOrAlias: number | string | string[], options: waitOptions) {
332359
// check to ensure options is an object
333360
// if its a string the user most likely is trying
334361
// to wait on multiple aliases and forget to make this
@@ -342,19 +369,18 @@ export default (Commands, Cypress, cy, state) => {
342369
}
343370

344371
options = _.defaults({}, options, { log: true })
345-
const args: any = [subject, msOrAlias, options]
346372

347373
try {
348-
if (_.isFinite(msOrAlias)) {
349-
return waitNumber.apply(window, args)
374+
if (typeof msOrAlias === 'number' && _.isFinite(msOrAlias)) {
375+
return waitNumber.apply(window, [subject, msOrAlias, options])
350376
}
351377

352378
if (_.isString(msOrAlias) || (_.isArray(msOrAlias) && !_.isEmpty(msOrAlias))) {
353379
if (Cypress.isCrossOriginSpecBridge) {
354-
return delegateToPrimaryOrigin(args)
380+
return delegateToPrimaryOrigin(msOrAlias, options)
355381
}
356382

357-
return waitString.apply(window, args)
383+
return waitString.apply(window, [msOrAlias, options])
358384
}
359385

360386
// figure out why this error failed
@@ -370,7 +396,7 @@ export default (Commands, Cypress, cy, state) => {
370396
throwErr(msOrAlias.toString())
371397
}
372398

373-
let arg
399+
let arg: string
374400

375401
try {
376402
arg = JSON.stringify(msOrAlias)
@@ -379,7 +405,7 @@ export default (Commands, Cypress, cy, state) => {
379405
}
380406

381407
return throwErr(arg)
382-
} catch (err: any) {
408+
} catch (err) {
383409
if (err.name === 'CypressError') {
384410
throw err
385411
} else {

packages/driver/src/cypress/state.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface StateFunc {
3333
(k: 'isStable', v?: boolean): boolean
3434
(k: 'whenStable', v?: null | (() => Promise<any>)): () => Promise<any>
3535
(k: 'current', v?: $Command): $Command
36-
(k: 'canceld', v?: boolean): boolean
36+
(k: 'canceled', v?: boolean): boolean
3737
(k: 'error', v?: Error): Error
3838
(k: 'assertUsed', v?: boolean): boolean
3939
(k: 'currentAssertionUserInvocationStack', v?: string): string
@@ -54,6 +54,7 @@ export interface StateFunc {
5454
(k: 'promise', v?: Bluebird<unknown>): Bluebird<unknown>
5555
(k: 'reject', v?: (err: any) => any): (err: any) => any
5656
(k: 'cancel', v?: () => void): () => void
57+
(k: 'aliasRequests', v?: Record<string, number>): Record<string, number>
5758
(k: string, v?: any): any
5859
state: StateFunc
5960
reset: () => Record<string, any>

packages/driver/types/cypress/log.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ declare namespace Cypress {
88
_hasInitiallyLogged: boolean
99
get<K extends keyof InternalLogConfig>(attr: K): InternalLogConfig[K]
1010
get(): InternalLogConfig
11-
set<K extends keyof LogConfig | InternalLogConfig>(key: K, value: LogConfig[K]): InternalLog
11+
set<K extends keyof LogConfig | keyof InternalLogConfig>(key: K, value: LogConfig[K]): InternalLog
1212
set(options: Partial<LogConfig | InternalLogConfig>)
1313
groupEnd(): void
1414
}
@@ -144,5 +144,6 @@ declare namespace Cypress {
144144
visible?: boolean
145145
// the timestamp of when the command started
146146
wallClockStartedAt?: string
147+
options?: unknown
147148
}
148149
}

0 commit comments

Comments
 (0)