How to Test Anchor Ping

How to confirm the browser sends POST request when you use anchor ping attribute.

If you add ping attribute to your anchor elements like this <a ping="/track" ...>, the any time the user clicks on the anchor, the browser sends POST request to the /track endpoint. This blog post shows how to confirm this behavior using Cypress.

Video

If you prefer learning about the ping attribute by watching a short video, I have recorded this:

Application

🧭 You can find this application example and described tests in the Stubbing using cy.intercept recipe.

Our application has an anchor element with ping attribute

1
<a href="/page2.html" ping="/track">Go to page 2 (with ping)</a>

We can see the browser sending the request to the server when we go from the first page using the link. Since we do not have a backend responding the the POST /track call, the browser receives 404.

Navigating through an anchor with ping attribute

If we inspect the /track request, we can find two request headers that track the click: Ping-From and Ping-To.

Tracking request headers

Can we confirm the tracking request is happening? We need some tests.

The test

In our test, we need to visit the page, set up the intercept, click on the button, and then check the intercept. Since there is no backend right now, we can use a network stub. Here is our test.

cypress/integration/ping-spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
describe('intercept', () => {
it('stubs anchor ping', () => {
cy.visit('/')
cy.intercept({
method: 'POST',
pathname: '/track',
}, {}).as('ping')

cy.get('a[ping]').click()
cy.location('pathname').should('equal', '/page2.html')
cy.wait('@ping')
})
})

The test passes

Passing test confirms the browser sends the tracking request

Let's also confirm the tracking request has the expected Ping-From and Ping-to headers. Note the headers are normalized to lowercase in the intercept:

1
2
3
4
5
cy.wait('@ping')
.its('request.headers').should('deep.include', {
'ping-from': 'http://localhost:7080/',
'ping-to': 'http://localhost:7080/page2.html',
})

The above test confirms the request headers include the above two lines

Confirming the headers

The Firefox exception

The anchor ping attribute is widely supported by the modern browsers, except the Firefox browser puts it behind a flag for privacy reasons.

Anchor ping feature as reported by caniuse.com

Thus our test can run in every browser but firefox. We can skip it using pre-test configuration

1
2
3
it('stubs anchor ping', { browser: '!firefox' }, () => {
...
})

When we run this spec in Firefox browser we see it skipped

Firefox skips the test

The Firefox flag

What if we do want to run the test in the Firefox browser? We could enable the "browser.send_pings" flag by opening a new tab at "about:config", finding the flag, and setting it to true.

Enable browser.send_pings to make the test pass in Firefox

Great, we can flip the flag manually, but what about CI? Can we set this Firefox feature flag programmatically?

Yes, we can - via browser launch API. Inside the plugin file we can flip the flag:

cypress/plugins/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.family === 'firefox') {
// One of the tests uses <a ping="..."> feature that
// is behind a flag in Firefox browser.
// We can programmatically enable an option
// in Firefox using launch options
launchOptions.preferences['browser.send_pings'] = true
}

// whatever you return here becomes the launchOptions
return launchOptions
})
}

The anchor ping test running in headless Firefox browser

Now our tests pass on CI in all browsers.