Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Recipe | Description
[Visiting 2nd domain](./examples/server-communication__visit-2nd-domain) | Visiting two different domains from two different tests
[Stream test results](./examples/server-communication__stream-tests) | Streams each test result from the browser to the plugins to an external process via IPC
[Offline](./examples/server-communication__offline) | Test web application when the network is offline
[Server timing](./examples/server-communication__server-timing) | Report server timing results from Cypress test

## Other Cypress Recipes

Expand Down
6 changes: 6 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ jobs:
<<: *defaults
server-communication__offline:
<<: *defaults
server-communication__server-timing:
<<: *defaults
fundamentals__dynamic-tests:
<<: *defaults
fundamentals__module-api:
Expand Down Expand Up @@ -540,6 +542,9 @@ all_jobs: &all_jobs
- server-communication__offline:
requires:
- build
- server-communication__server-timing:
requires:
- build
- fundamentals__dynamic-tests:
requires:
- build
Expand Down Expand Up @@ -610,6 +615,7 @@ all_jobs: &all_jobs
- server-communication__visit-2nd-domain
- server-communication__stream-tests
- server-communication__offline
- server-communication__server-timing
- stubbing-spying__functions
- stubbing-spying__window-fetch
- stubbing-spying__intercept
Expand Down
30 changes: 30 additions & 0 deletions examples/server-communication__server-timing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Server Timing
> Show server timing results from Cypress visit

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.

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.

![Print server timings](images/print.png)

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)

![Log server timings](images/log.png)

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:

```js
window.performance.getEntriesByType('navigation')[0]serverTiming
```

Which in Cypress test can be done

```js
cy.window().its('performance')
.invoke('getEntriesByType', 'navigation')
.its('0.serverTiming')
```

We can then find the desired duration and assert it is below certain limit

![Checking performance](images/limit.png)
6 changes: 6 additions & 0 deletions examples/server-communication__server-timing/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"baseUrl": "http://localhost:3000",
"fixturesFolder": false,
"supportFile": false,
"pluginsFile": false
}
22 changes: 22 additions & 0 deletions examples/server-communication__server-timing/cypress/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Cypress.io end-to-end tests

[Cypress.io](https://www.cypress.io) is an open source, MIT licensed end-to-end test runner

## Folder structure

These folders hold end-to-end tests and supporting files for the Cypress Test Runner.

- [fixtures](fixtures) holds optional JSON data for mocking, [read more](https://on.cypress.io/fixture)
- [integration](integration) holds the actual test files, [read more](https://on.cypress.io/writing-and-organizing-tests)
- [plugins](plugins) allow you to customize how tests are loaded, [read more](https://on.cypress.io/plugins)
- [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)

## `cypress.json` file

You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration).

## More information

- [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress)
- [https://docs.cypress.io/](https://docs.cypress.io/)
- [Writing your first Cypress test](http://on.cypress.io/intro)
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// enables intelligent code completion for Cypress commands
// https://on.cypress.io/intelligent-code-completion
/// <reference types="cypress" />

/* eslint-disable no-console */

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
// for now assume it is always something like "cache;desc="my example";dur=2002"
const parseTiming = (singleValue) => {
const parts = singleValue.split(';')

return {
name: parts[0],
description: parts[1].split('"')[1],
duration: parseFloat(parts[2].split('=')[1]), // in ms
}
}

const parseServerTimings = (value) => {
return value.split(',')
.map((s) => s.trim())
.map(parseTiming)
}

const logTiming = (timing) => {
return cy.log(`**${timing.name}** ${timing.description} ${timing.duration}ms`)
}

describe('Server', () => {
it('reports timings', function () {
cy.intercept('/').as('document')
cy.visit('/')
cy.wait('@document').its('response.headers.server-timing')
.then(parseServerTimings)
.then(console.table)
})

it('logs timings', function () {
cy.intercept('/').as('document')
cy.visit('/')
cy.wait('@document').its('response.headers.server-timing')
.then(parseServerTimings)
.then((timings) => {
timings.forEach(logTiming)
})
})

it('reports timings using performance entry', () => {
cy.visit('/')
cy.window().its('performance')
.invoke('getEntriesByType', 'navigation')
.its('0.serverTiming.0')
.then(logTiming)

// we can even assert that the duration is below certain limit
cy.window().its('performance')
.invoke('getEntriesByType', 'navigation')
.its('0.serverTiming')
.then((timings) => {
// find the cache timing among all server timings
return Cypress._.find(timings, { name: 'cache' })
})
.then((cacheTiming) => {
expect(cacheTiming).property('duration').to.be.lessThan(2100)
})
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions examples/server-communication__server-timing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable no-console */
const { send, createError } = require('micro')

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
const formatServerTiming = (items) => {
const toTiming = (item) => {
return `${item.name};desc="${item.description}";dur=${item.duration}`
}

return items.map(toTiming).join(', ')
}

module.exports = async (req, res) => {
if (req.url === '/favicon.ico') {
return send(res, 404)
}

if (req.url === '/') {
res.setHeader('Content-Type', 'text/html')
const value = formatServerTiming([{
name: 'cache',
description: 'my example',
duration: 2002,
}])

res.setHeader('Server-Timing', value)

return send(res, 200, '<body><h1>Hi there</h1></body>')
}

console.log('req "%s"', req.url)
throw createError(500, 'Unknown path')
}
15 changes: 15 additions & 0 deletions examples/server-communication__server-timing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "server-timing",
"version": "1.0.0",
"description": "Show server timing results from Cypress visit",
"private": true,
"main": "index.js",
"scripts": {
"cypress:open": "../../node_modules/.bin/cypress open",
"cypress:run": "../../node_modules/.bin/cypress run",
"dev": "../../node_modules/.bin/start-test 3000 cypress:open",
"start": "../../node_modules/.bin/micro"
},
"license": "MIT",
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/// <reference types="cypress" />
/* eslint-disable no-console */
describe('XHR', () => {

// prevent rare flake in these tests due to json-server
// not being very strong when hit from multiple clients
describe('XHR', { retries: 3 }, () => {
it('sends XHR to the server and gets expected response', () => {
cy.visit('index.html')

Expand Down
22 changes: 21 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"jsonwebtoken": "8.5.1",
"lit-element": "2.3.1",
"lodash": "4.17.19",
"micro": "9.3.4",
"minimist": "1.2.3",
"morgan": "1.9.1",
"prop-types": "15.6.2",
Expand Down