Cypress

From bibbleWiki
Jump to navigation Jump to search

Introduction

This is notes of my usage of cypress

Configuration

When I installed Cypress it was v12 of the product. This has changed slightly from most of the tutorials as the name of the tests no longer have spec and but cy and reside in e2e rather than the integration folder. The other change was that Cypress has all tests isolated by default. I.E. it resets the browser in between tests

import { defineConfig } from 'cypress'

export default defineConfig({
   testIsolation: false,
})
~                                                                                                                                                                 
~

Cypress REST API

Perhaps an unusual usage but I use the to validate my OpenAPI 3.x swagger files used in Node. You will need to read the docs but an example of a openAPI 3.0 is provided below. The software cypress-swagger-validation validates OpenAPI 3.x swagger schema definitions against the actual API results. I can be install with

npm i @jc21/cypress-swagger-validation -D

Swagger Set up

I use swagger-ui-express which allows you to serve the swagger.json.

    expressInstance.get(`${prefix}/docs/swagger.json`, (req, res) => res.json(swaggerConfig))
    expressInstance.use(`${prefix}/docs`, swaggerUi.serve, swaggerUi.setup(swaggerConfig, options))

Swagger File

Here is the user swagger file used with swagger and swagger-ui. Cypress will be used to validate the schema is correct.

import { standardResponses } from './standardResponses'
export const userPaths = {
    '/users': {
        get: {
            summary: 'Get all Users',
            operationId: 'listUsers',
            description: 'Get all Users',
            produces: ['application/json'],
            parameters: [],
            responses: {
                '200': {
                    description: 'Array of User',
                    content: {
                        'application/json': {
                            schema: {
                                $ref: '#/components/schemas/Users',
                            },
                        },
                    },
                },
                ...standardResponses,
            },
        },
    },
}

export const userSchemas = {
    User: {
        required: ['USER_SECURITY_ID', 'USER_NAME', 'USER_FULL_NAME', 'USER_EMAIL'],
        properties: {
            USER_SECURITY_ID: {
                type: 'string',
            },
            USER_NAME: {
                type: 'string',
            },
            USER_FULL_NAME: {
                type: 'string',
            },
            USER_EMAIL: {
                type: 'string',
            },
        },
    },
    Users: {
        type: 'array',
        items: {
            $ref: '#/components/schemas/User',
        },
    },
}

SSL Certificates

I use https for calls to the REST API. To use SSL you can config cypress in the config file

export default defineConfig({
    clientCertificates: [
        {
            url: 'https://localhost',
            ca: ['certs/rootCA.pem'],
            certs: [
                {
                    cert: './certs/server.crt',
                    key: './certs/server.key',
                },
            ],
        },
    ],
...
})

OAuth Set up

We need to get the access token for each call. To do this we setup the environment variables and create a function to get the token. In the cypress.config.ts set we config the environment

import dotenv from 'dotenv'
dotenv.config()

export default defineConfig({
...
    env: {
        auth0_tenant_id: process.env.API_TENANT_ID,
        auth0_host: process.env.API_OAUTH_HOST,
        auth0_scope: process.env.API_SCOPE,
        auth0_client_id: process.env.API_CLIENT_ID,
        auth0_client_secret: process.env.API_CLIENT_SECRET,
    },
...
})

We then create a function to get the certificate.

export const prefix = 'https://localhost:3016/api/v0.0.1'
export const swaggerFilePrefix = `${prefix}/docs`
export const swaggerFile = `${swaggerFilePrefix}/swagger.json`

export const getOptions = (endPoint: string): any => {
    const token = Cypress.env('token') as string
    const authorization = `bearer ${token}`
    const options = {
        method: 'GET',
        url: `${prefix}/${endPoint}`,
        headers: {
            authorization,
        },
    }
    return options
}

Test

We use MSAL oauth to for authentication. The getOptions gets the token and puts the bearer token in the request. Then the validateSwaggerSchema validates the schema against the result

import { getOptions, swaggerFile } from './helper'

describe('User Test', () => {
    before(() => {
        cy.getOauthAccessToken().then(oAuthResp => {
            Cypress.env('token', oAuthResp.body.access_token)
            // cy.task('log', `Token is ${oAuthResp.body.access_token}`)
        })
    })

    it('User Get Payload', () => {
        cy.request(getOptions('users')).then($response => {
            // Check the swagger schema:
            cy.task('validateSwaggerSchema', {
                file: swaggerFile, // optional path or full URL, see below
                endpoint: `/users`,
                method: 'get',
                statusCode: 200,
                responseSchema: $response.body,
                verbose: true, // optional, default: false
            }).should('equal', null)
        })
    })
})

Configuration

tsconfg

Could not get my scripts to run and produced the following error

TSError: ⨯ Unable to compile TypeScript:
error TS5095: Option 'bundler' can only be used when 'module' is set to 'es2015' or later.

To fix add

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Node 18",

  "_version": "18.2.0",

  "compilerOptions": {
    "lib": ["es2023", "ES2023.Array"],
    "module": "esNext",
    "target": "esNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "Bundler",

    "outDir": "./dist",
    "rootDir": "./src"
  },

  "ts-node": {
    "compilerOptions": {
      "module": "ESNext",
      "moduleResolution": "Node"
    }
  },

  "include": ["src/**/*"],
  "exclude": ["./node_modules", "dist"]
}

Run Single Test

npx cypress run --spec cypress/e2e/user.cy.ts

Add Logging to test

Seems tricky, I guess because of redirecting but cy.task("log",".....") does the trick

    it("User Get Payload", () => {
        cy.task("log", "=============> Logging user test")
        cy.request(getOptions("users")).then($response => {
            // Check the swagger schema:
            cy.task("validateSwaggerSchema", {
                file: swaggerFile, // optional path or full URL, see below
                endpoint: `/users`,
                method: "get",
                statusCode: 200,
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                responseSchema: $response.body,
                verbose: true, // optional, default: false
            }).should("equal", null)
        })
    })