Let's say you need to specify a timeout or wait in your end-to-end Cypress tests. You would use milliseconds
1 | // try finding the "selector" elements for up to 1 second |
For clarity, you could write a helper to convert seconds into milliseconds
1 | const seconds = (n) => n * 1000 |
What happens if you accidentally forget the seconds(...) call? You still get a spec that passes your linter
1 | const seconds = (n) => n * 1000 |
TypeScript system would prevent you from accidentally passing a string into cy.wait(...) for example, but not from mixing the units: milliseconds vs seconds, cents vs dollars, dollars vs euros. If you want to distinguish units, you could use "branded types" approach. You could read about branded types here and here and follow this blog post.
Ms vs Seconds branded types
Let's extend number type with a "brand" property using number & { __brand: T } syntax. We will create one type for milliseconds and another type for seconds. I will put the types into cypress/support/index.d.ts file so it is available in all specs by default.
1 | // cypress/support/index.d.ts |
🎓 This blog post is based on the exercise in my "Testing The Swag Store" online course available at cypress.tips/courses.
Now let's create a custom command to "replace" the built-in cy.wait command. Our command will explicitly take Milliseconds argument
1 | // cypress/support/index.d.ts |
The implementation simply delegates to cy.wait
1 | // cypress/support/commands.ts |
Using branded types
Let's use cy.delay(ms) from our spec file
1 | describe('Waiting with branded types', () => { |
The code works. At runtime, 500 is a valid argument to pass from cy.delay(500) to cy.wait(500), but what does our TypeScript say about it? It complains:
1 | > tsc --noEmit --pretty |
We can't simply pass a number where Milliseconds is expected, since a number does not have the & { __brand: T } type part. We need to be explicit: we know 500 is a milliseconds value.
1 | // write equivalent to "cy.wait(500)" |
There are no more type errors

Let's create a helper to convert seconds into milliseconds
1 | /** |
We need to import this function from our spec to use
1 | import { seconds } from '..' |
Again, we need to be explicit when introducing 3 - it is a value of seconds.
Branded type predicate
If we want to allow any positive number to be used as milliseconds, we could use a "type predicate"
1 | /** |
The part n is Milliseconds tells TypeScript that if the function returns true, the argument can be used as type Milliseconds. Thus in the if branch below, the n value can by used with cy.delay(n) command without a type error.
1 | import { seconds, isMilliseconds } from '..' |
Here is what the TS IntelliSense shows on the const n = 3_000 line

And here is what TS IntelliSense shows inside the if (isMilliseconds(n)) branch

Branded type assertion
Using if (isMilliseconds(n)) every time we want to type n as milliseconds is tiresome. Let's add one more utility function to assert that a given argument is of type Milliseconds
1 | /** |
The crucial part is the asserts n is Milliseconds syntax. Let's use this function in our spec
1 | import { seconds, assertMilliseconds } from '..' |
Hover over n after the assertMilliseconds(n) line to confirm that TypeScript "knows" that n is Milliseconds branded type now

The branded types approach works for resolving confusion when using currency, number of machines, ids, etc in your specs. Of course, there are libraries that implement branded types so you do not have to create your own: ts-brand and Effect.
😅 Fun fact: I was so sure I wrote this blog post about branded types many years ago, that I kept searching my blog posts and repos for it in vain. "I remember reading about branded types and writing my own take on using them in end-to-end specs, I am sure my blog has it" - until an exhaustive check showed that I simply read about branded types, but never wrote down my own take.