This blog post shows how to validate the URL search parameters (the part of the URL after the question mark, like ?id=123&name=Gleb
) from a Cypress test.
Example application
For this blog post I will use an online e-store application from the Testing The Swag Store online course. When the user adds an item to the cart, the URL changes to include the item's ID and count (both are pretend) in its search params like this ?count=1&itemId=...
.
Here is the starting code for this blog post: we simply want to log in and click the "Add to cart" button.
1 | import { LoginPage } from '@support/pages/login.page' |
Let's validate the count
and item
values the application sets in the URL search params.
Check the URL search string
Cypress has two queries that return the URL information: cy.url and cy.location. I have seen people mostly using cy.url
command, but I prefer using the cy.location
. The cy.location
returns any part of the parsed URL: the host, the full URL, the search part. In fact, cy.url
is simply an alias to cy.location('href')
command!
1 | cy.url() // yields "https://acme.co/page/foo.html?count=... |
Without any arguments, cy.location
yields an object with all URL parsed parts
1 | cy.location().should(loc => { |
Ok, let's simply check the search property as a string.
1 | it('controls the URL search params (check the search string)', () => { |
The test checks the search
URL property with retries because it follows the form QUERY . SHOULD(assertion)
.
The parameters order
In our application the order of search params is NOT guaranteed. Sometimes it is count=...&item=...
and other times it is item=...&count=...
. Thus our solution would fail sometimes. The test is flaky. We can improve it by listing the two possible orders.
1 | it('controls the URL search params (check the search string)', () => { |
Great, but the assertion does not show the possible matches, and could fail if we have more parameters, or some unknown parameters that our test should not verify.
Multiple assertions
Let's avoid checking the entire URL search params string and check individual values instead. We are interested in the count=...
and the item=...
parameters, so let's attach multiple assertions to the same cy.location('search')
query.
1 | it('controls the URL search params (multiple assertions)', () => { |
The test retries because it follows the form QUERY . SHOULD(assertion1) . AND(assertion2) ...
. The .should(...)
and .and(...)
assertion commands are equivalent and are used for better readability.
The separate assertions are much clearer in the Command Log.
Using URLSearchParams
Instead of checking substrings, we could let the browser parse and process the URL. There is a built-in standard for URL parsing that we can use via URLSearchParams object. We can create an instance of URLSearchParams
and get the individual parameters as strings inside a should(callback)
function:
1 | it('controls the URL search params (parse URLSearchParams)', () => { |
If we want to verify all parameters irrespective of their order, we can grab all properties of the parsed search params object, make an object, and use deep.equal
assertion to check them:
1 | it('has only count and item search params', () => { |
Tip: if you only know some parameters, you can use deep.include
assertion instead of deep.equal
.
In both cases, I prefer using URLSearchParams
to checking strings. The URL parsing might be tricky if the parameters are encoded, so don't do it yourself.
URLSearchParams
without should(callback)
We can improve the readability of the above test by breaking apart the single should(callback)
. If you looks at the assertion callback function it:
- takes the current subject (a string) and constructs an instance of
URLSearchParams
- calls
Object.fromEntries
to make a plain object fromURLSearchParams
instance - checks the plain object using an assertion
We can rewrite the callback function into a series of queries that transform the subject (the URL search string) and run an assertion. Cypress lacks the necessary queries, but I can use my plugin cypress-map to fill the missing pieces.
1 | import 'cypress-map` |
A little explanation of each step in the above test:
cy.location('search')
yields a string.make(URLSearchParams)
takes the string subject and constructs an object usingnew URLSearchParams(s)
.toPlainObject('entries')
takes the object and yieldsObject.fromEntries(o)
- we map strings in the current subject to numbers for exact comparison later using
1
2
3
4.map({
count: Number,
item: Number,
}) - the last assertion checks the properties of the current subject
1
2
3
4.should('deep.equal', {
count: 1,
item: itemId,
})
If the assertion fails, it goes back all the way to cy.location('search')
query and the steps run again. Beautiful.
🎓 This blog post gives examples from the lesson Bonus 64: Validate URL search params of the Testing The Swag Store online course. Often the URL parameters are URL encoded and thus you need to account for this in your assertions. See the lesson Bonus 65: confirm the escaped URL search params for hands-on exercises showing how to do it reliably.