The Rendered Font

How to query the element to find out what font was really rendered.

This blog post shows another example automation use case in Cypress Test Runner via its Chrome Debugger Protocol connection. Let's take a page that uses a custom fancy font from Google Fonts site to render the body text. You can find this example page in the repo bahmutov/fastify-example.

public/fancy.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>Fancy</title>
<link
href="https://fonts.googleapis.com/css2?family=Satisfy&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: 'Satisfy', cursive;
margin: 2rem 4rem;
}
</style>
</head>
<body>
<h1>Fancy Page</h1>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae
expedita dolore natus itaque cum, exercitationem consequatur possimus,
illo dignissimos fugiat a deserunt sunt, sed maiores libero rem eveniet
aspernatur. Omnis.
</p>
</body>
</html>

How does the page look while the font is loading? Using the cy.intercept command we can slow down loading either the initial CSS resource, or the actual font (see the bonus lesson in my Cypress network testing course to learn how to slow down network calls). While the font "Satisfy" is loading, the browser shows the fallback font family "cursive" which loads the system font from my laptop.

The test delays by two seconds loading the custom font

How do we confirm the font "Satisfy" really loads? If we ask the browser what the font should be used, it will answer "Satisfy" right away. The DOM simply returns what we listed in the element's styles. Let's write a test that uses the Chai-jQuery assertion "have.css"

1
2
3
4
5
6
7
it('shows the font listed in the style', () => {
cy.visit('/fancy.html')
cy.get('body')
.should('have.css', 'font-family')
.then(cy.log)
.should('include', 'Satisfy')
})

Hmm, the .should('have.css', 'font-family') assertion yields the "Satisfy, cursive" style string, not the actual rendered font name. You can see how the test passes, even if the laptop has its network WiFi turned off.

The test checks the font listed in the body style

The font "Satisfy" does not even load, yet the test only looks at the listed style. How do we get the actual rendered font name?

Rendered font in DevTools

If you inspect the element in the Chrome DevTools, you can find the "rendered font" name after the font successfully loads and is applied to the element. The system font name is available in the DevTools Elements Tab at the bottom of the "Computed" properties sub tab.

The DevTools shows the current rendered font name

Let's get this rendered font name from the test. It takes a few calls via Cypress.automation low-level command which uses the existing Cypress Automation Chrome Debugger Protocol connection to send CDP commands.

1
2
3
4
5
6
7
8
9
// Cypress.automation returns a Promise
Cypress.automation('remote:debugger:protocol',
{
command: '... CDP command ...',
params: {
// command params
},
},
)

I will use my cypress-cdp plugin to get the element's Node ID we need to fetch the rendered fonts using CDP command.

1
2
$ npm i -D cypress-cdp
+ [email protected]

From the spec file I import the plugin and use a Chrome-based browser to run the test. We need to get the rendered font, which we can do using the Chrome Debugger Protocol command CSS.getPlatformFontsForNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import 'cypress-cdp'

it('confirms the rendered font after a delay', () => {
// disable network caching
cy.CDP('Network.setCacheDisabled', {
cacheDisabled: true,
})
// slow down the font load
cy.intercept(
{
hostname: 'fonts.gstatic.com',
},
() => Cypress.Promise.delay(2000),
)
cy.visit('/fancy.html')
// wait for the rendered font to be "Satisfy"
cy.getCDPNodeId('body').then((nodeId) => {
cy.CDP('CSS.getPlatformFontsForNode', {
nodeId,
}).should((result) => {
expect(result.fonts)
.to.be.an('array')
.and.to.have.length(1)
expect(
result.fonts[0].familyName,
'font family',
).to.equal('Satisfy')
})
})
})

The test retries fetching the rendered font until the real font "Satisfy" loads and is used by the browser to render the text in the <body> element.

The test confirming that the remote custom font "Satisfy" is used to render the text

Nice!

See also