Skip to content

Commit 16e9ca4

Browse files
authored
add recipe that shows server timing (#593)
1 parent b7e65b3 commit 16e9ca4

File tree

14 files changed

+206
-2
lines changed

14 files changed

+206
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Recipe | Description
115115
[Visiting 2nd domain](./examples/server-communication__visit-2nd-domain) | Visiting two different domains from two different tests
116116
[Stream test results](./examples/server-communication__stream-tests) | Streams each test result from the browser to the plugins to an external process via IPC
117117
[Offline](./examples/server-communication__offline) | Test web application when the network is offline
118+
[Server timing](./examples/server-communication__server-timing) | Report server timing results from Cypress test
118119

119120
## Other Cypress Recipes
120121

circle.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ jobs:
298298
<<: *defaults
299299
server-communication__offline:
300300
<<: *defaults
301+
server-communication__server-timing:
302+
<<: *defaults
301303
fundamentals__dynamic-tests:
302304
<<: *defaults
303305
fundamentals__module-api:
@@ -540,6 +542,9 @@ all_jobs: &all_jobs
540542
- server-communication__offline:
541543
requires:
542544
- build
545+
- server-communication__server-timing:
546+
requires:
547+
- build
543548
- fundamentals__dynamic-tests:
544549
requires:
545550
- build
@@ -610,6 +615,7 @@ all_jobs: &all_jobs
610615
- server-communication__visit-2nd-domain
611616
- server-communication__stream-tests
612617
- server-communication__offline
618+
- server-communication__server-timing
613619
- stubbing-spying__functions
614620
- stubbing-spying__window-fetch
615621
- stubbing-spying__intercept
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Server Timing
2+
> Show server timing results from Cypress visit
3+
4+
Imagine the server collects its performance timings while preparing and serving the `/` page. The server can send these timings using [Server-Timing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) header (see [index.js](index.js)), and we can inspect these numbers from the Cypress test. See [cypress/integration/spec.js](cypress/integration/spec.js) file.
5+
6+
We can intercept the `/` load using [cy.intercept](https://on.cypress.io/intercept) and inspect the response's headers. We have to find the `server-timing` header and parse it ourselves.
7+
8+
![Print server timings](images/print.png)
9+
10+
Printing to the DevTools console hides the numbers, so instead we can print them to the Command Log using [cy.log](https://on.cypress.io/log)
11+
12+
![Log server timings](images/log.png)
13+
14+
We can also go about server timings the other way. Instead of spying on the `/` resource, we can let the page load and then access the performance object collected by the browser automatically. For example, we can get all server timings already parsed by the browser by accessing them:
15+
16+
```js
17+
window.performance.getEntriesByType('navigation')[0]serverTiming
18+
```
19+
20+
Which in Cypress test can be done
21+
22+
```js
23+
cy.window().its('performance')
24+
.invoke('getEntriesByType', 'navigation')
25+
.its('0.serverTiming')
26+
```
27+
28+
We can then find the desired duration and assert it is below certain limit
29+
30+
![Checking performance](images/limit.png)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"baseUrl": "http://localhost:3000",
3+
"fixturesFolder": false,
4+
"supportFile": false,
5+
"pluginsFile": false
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Cypress.io end-to-end tests
2+
3+
[Cypress.io](https://www.cypress.io) is an open source, MIT licensed end-to-end test runner
4+
5+
## Folder structure
6+
7+
These folders hold end-to-end tests and supporting files for the Cypress Test Runner.
8+
9+
- [fixtures](fixtures) holds optional JSON data for mocking, [read more](https://on.cypress.io/fixture)
10+
- [integration](integration) holds the actual test files, [read more](https://on.cypress.io/writing-and-organizing-tests)
11+
- [plugins](plugins) allow you to customize how tests are loaded, [read more](https://on.cypress.io/plugins)
12+
- [support](support) file runs before all tests and is a great place to write or load additional custom commands, [read more](https://on.cypress.io/writing-and-organizing-tests#Support-file)
13+
14+
## `cypress.json` file
15+
16+
You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration).
17+
18+
## More information
19+
20+
- [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress)
21+
- [https://docs.cypress.io/](https://docs.cypress.io/)
22+
- [Writing your first Cypress test](http://on.cypress.io/intro)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// enables intelligent code completion for Cypress commands
2+
// https://on.cypress.io/intelligent-code-completion
3+
/// <reference types="cypress" />
4+
5+
/* eslint-disable no-console */
6+
7+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
8+
// for now assume it is always something like "cache;desc="my example";dur=2002"
9+
const parseTiming = (singleValue) => {
10+
const parts = singleValue.split(';')
11+
12+
return {
13+
name: parts[0],
14+
description: parts[1].split('"')[1],
15+
duration: parseFloat(parts[2].split('=')[1]), // in ms
16+
}
17+
}
18+
19+
const parseServerTimings = (value) => {
20+
return value.split(',')
21+
.map((s) => s.trim())
22+
.map(parseTiming)
23+
}
24+
25+
const logTiming = (timing) => {
26+
return cy.log(`**${timing.name}** ${timing.description} ${timing.duration}ms`)
27+
}
28+
29+
describe('Server', () => {
30+
it('reports timings', function () {
31+
cy.intercept('/').as('document')
32+
cy.visit('/')
33+
cy.wait('@document').its('response.headers.server-timing')
34+
.then(parseServerTimings)
35+
.then(console.table)
36+
})
37+
38+
it('logs timings', function () {
39+
cy.intercept('/').as('document')
40+
cy.visit('/')
41+
cy.wait('@document').its('response.headers.server-timing')
42+
.then(parseServerTimings)
43+
.then((timings) => {
44+
timings.forEach(logTiming)
45+
})
46+
})
47+
48+
it('reports timings using performance entry', () => {
49+
cy.visit('/')
50+
cy.window().its('performance')
51+
.invoke('getEntriesByType', 'navigation')
52+
.its('0.serverTiming.0')
53+
.then(logTiming)
54+
55+
// we can even assert that the duration is below certain limit
56+
cy.window().its('performance')
57+
.invoke('getEntriesByType', 'navigation')
58+
.its('0.serverTiming')
59+
.then((timings) => {
60+
// find the cache timing among all server timings
61+
return Cypress._.find(timings, { name: 'cache' })
62+
})
63+
.then((cacheTiming) => {
64+
expect(cacheTiming).property('duration').to.be.lessThan(2100)
65+
})
66+
})
67+
})
104 KB
Loading
139 KB
Loading
328 KB
Loading
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable no-console */
2+
const { send, createError } = require('micro')
3+
4+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
5+
const formatServerTiming = (items) => {
6+
const toTiming = (item) => {
7+
return `${item.name};desc="${item.description}";dur=${item.duration}`
8+
}
9+
10+
return items.map(toTiming).join(', ')
11+
}
12+
13+
module.exports = async (req, res) => {
14+
if (req.url === '/favicon.ico') {
15+
return send(res, 404)
16+
}
17+
18+
if (req.url === '/') {
19+
res.setHeader('Content-Type', 'text/html')
20+
const value = formatServerTiming([{
21+
name: 'cache',
22+
description: 'my example',
23+
duration: 2002,
24+
}])
25+
26+
res.setHeader('Server-Timing', value)
27+
28+
return send(res, 200, '<body><h1>Hi there</h1></body>')
29+
}
30+
31+
console.log('req "%s"', req.url)
32+
throw createError(500, 'Unknown path')
33+
}

0 commit comments

Comments
 (0)