3 minute read

Recently, I was involved to setup the e2e testing for the project at work. It’s usually a straightforward process to install cypress and get the test cases working. However, the challenge arises when I realised that the cypress test cases did not go through because the api endpoint is running on https, and the client have to supply a valid employee client certificate in order to get through the security of the api endpoint. In the dev/uat environment to test the application, we can easily have the certificate installed in chrome, and a popup to choose the certificate will automatically appear when the application needs to access the api, but we can’t do that in the e2e test as the browser is triggered by cypress, and there aren’t any chrome switches to inject my certificate.

The solution was to create a proxy server in the cypress plugin, so that the certificate can be injected. But I’ll have to redirect the api call to the proxy server instead. It’s not the most straightforward thing to do, so I’m documenting the process and knowhow here for future reference.

The source code to the solution is available at https://github.com/thecodinganalyst/cypress-proxy.

Cypress will create 4 folders after you run it for the first time, and 1 of those is the plugins folder, with an index.js. This is where the proxy server is to be created.

const http = require('http')
const httpProxy = require('http-proxy')
const fs = require('fs')

const proxyServer = httpProxy.createProxyServer({
    target: {
        protocol: 'https:',
        host: 'localhost',
        port: '8000',
        cert: fs.readFileSync('cert/client-crt.pem'),
        key: fs.readFileSync('cert/client-key.pem'),
        ca: fs.readFileSync('cert/ca-crt.pem')
    },
    changeOrigin: true,
    secure: false
})

const server = http.createServer((req, res) => {
    proxyServer.web(req, res)
})

server.listen(9000)

Copy the above code above the module.exports in cypress/plugins/index.js. The ca field is probably not needed in your corporate environment as your server and client certificates will already by installed and trusted. I’m using a self-signed certificate here, so using the certificate generated by my self-signed certificate authority (ca), and specifying the ca here will make things easier.

To run the cypress test, first open a terminal to start the https server - node index.js. Then open another terminal to run cypress - npx cypress open.

Before going to this solution, I have created a server.js to represent the https endpoint, and a proxy.js as the proxy server, to ensure the solution works before I apply it in cypress. The server.js is just a very simple https server, listening on port 8000, which returns a “Hello World”.

const fs = require('fs')
const https = require('https')
const express = require('express')

const app = express()

app.get('/', (req, res) => {
    console.log(new Date() + ' ' + req.socket.remoteAddress + ' ' + req.method + ' ' + req.url)
    res.writeHead(200, {'Content-Type': 'text/html'})
    res.write('Hello World\n')
    res.end()
})

const server = https.createServer({
    key: fs.readFileSync('cert/server-key.pem'),
    cert: fs.readFileSync('cert/server-crt.pem'),
    ca: fs.readFileSync('cert/ca-crt.pem'),
    requestCert: true,
    rejectUnauthorized: true
}, app)

function start(port){
    server.listen(port)
}

function stop(){
    server.close()
}

module.exports.start = start
module.exports.stop = stop

It’s using the self-signed certificate by a self-signed CA. You can run the 8 .sh scripts in the repository to generate the certificates needed. Do note that as both the requestCert and rejectUnauthorized are true, you can’t simply curl -k https://localhost:8000 to reach the server. Accessing it by chrome is also prevented as chrome will reject navigating to the site as it is using a self-signed certificate. Run node index.js to start the https server.

The proxy.js sets up the proxy server in http port 9000, targeting our https 8000 server. So that if we navigate to http://localhost:9000/, it will redirect to https://localhost:8000/, and the certificate is injected, so we can safely open the http://localhost:9000/ in chrome. Run node start_proxy.js to start the proxy server. Then try to open http://localhost:9000 in your chrome browser. It should work.

const http = require('http')
const httpProxy = require('http-proxy')
const fs = require('fs')

const proxyServer = httpProxy.createProxyServer({
    target: {
        protocol: 'https:',
        host: 'localhost',
        port: '8000',
        cert: fs.readFileSync('cert/client-crt.pem'),
        key: fs.readFileSync('cert/client-key.pem'),
        ca: fs.readFileSync('cert/ca-crt.pem')
    },
    changeOrigin: true,
    secure: false
})

const server = http.createServer((req, res) => {
    proxyServer.web(req, res)
})

function start(port){
    server.listen(port)
}

function stop(){
    server.close()
}

module.exports.start = start
module.exports.stop = stop

Following good practise, the unit tests are created in the test folder, and running jest will automatically test the https server and the proxy server to ensure everything is working as it should.