Skip to content

Collection of commands and utilities to be used for automating tests for Cumulocity with Cypress.

License

Notifications You must be signed in to change notification settings

Cumulocity-IoT/cumulocity-cypress

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Cypress commands for Cumulocity

Collection of commands and utilities to be used for automating tests for Cumulocity IoT with Cypress. Also use the repository to discuss questions and issues when testing your Cumulocity IoT applications with Cypress.

Contribute by raising pull requests. All commands must be documented and, if possible, tested using test suite in this repository.

Content

Overview of commands

Current set of commands include

General commands

  • visitAndWaitForSelector
  • setLanguage
  • hideCookieBanner
  • disableGainsights

Authentication related commands

  • login
  • setAuth
  • useAuth
  • bootstrapDeviceCredentials
  • oauthLogin

Date related commands

  • toDate
  • toISODate
  • compareDates

Administration related commands

  • getCurrentTenant and getTenantId
  • createUser and deleteUser
  • assignUserRoles and clearUserRoles
  • getSystemVersion and getShellVersion

Component testing

  • mount

Integration and API testing related commands

  • c8yclient, c8yclientf
  • c8ymatch
  • retryRequest
  • request

See Integration and API testing for more information.

Screenshot automation

With the c8yscrn command, cumulocity-cypress provides a tool to automate taking screenshots of your Cumulocity IoT applications as part of your test suite or CI/CD workflows. See Screenshot automation for more information.

Installation and setup

Add dependency to your package.json and install via npm or yarn.

npm install --save-dev cumulocity-cypress

or

yarn add -D cumulocity-cypress

Peer dependencies

cumulocity-cypress requires some peer dependencies to be installed in your project for all commands to work as expected. This is to make sure the exact versions of the dependencies in your tested project are used by cumulocity-cypress.

Make sure the following dependencies are installed in your project:

  • cypress
  • @c8y/client
  • angular-common

Load plugin

cumulocity-cypress comes with a Cypress plugin that needs to be loaded in your cypress.config.ts file. Currently, this is only required for recording and mocking of requests and responses, but it is recommended to load the plugin in any case.

import { defineConfig } from "cypress";
import { configureC8yPlugin } from "cumulocity-cypress/plugin";

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      configureC8yPlugin(on, config);
      // important to return the config object
      return config;
    },
  },
});

Import commands

To use the cumulocity-cypress commands in your Cypress tests, import the commands in your projects e2e.supportFile (e.g. cypress/support/e2e.ts).

import "cumulocity-cypress/lib/commands";

This will import the standard commands, including for example login, authentication, date conversion, administration.

Optional commands for import (only import if really needed):

// Import extension for cy.request() to support authentication
import "cumulocity-cypress/lib/commands/request";
// Import commands for recording and mocking of requests and responses
import "cumulocity-cypress/lib/commands/c8ypact";
// Enable recording and mocking for cy.intercept()
import "cumulocity-cypress/lib/commands/intercept";

See API and Integration Testing for more information on how to enable recording and matching of requests and responses using cy.c8yclient and cy.intercept.

Import the mount command for component testing of Cumulocity Angular components.

import "cumulocity-cypress/lib/commands/mount";

Environment variables

The easiest way to configure environment variables is to create a cypress.env.json file in your project. Use this file to pass for example credentials needed for the tests or any other environment variable supported by cumulocity-cypress. Of course, environment variables can be set in any other way as described in Cypress documentation.

{
  "admin_username": "admin",
  "admin_password": "password",
  "noaccess_username": "noaccess",
  "noaccess_password": "password"
}

A list of supported environment variables can be found in Environment variables section of the API and Integration Testing documentation.

It is also recommended to init certain environment variables in a global before hook of your tests. C8Y_TENANT for example is used by cumulocity-cypress if a tenant id is required. Some commands like login, c8yclient or all administration commands require a tenant id. Setting the tenant id once in global before hook will make sure it is available for all tests.

before(() => {
  cy.getAuth("admin")
    .getCurrentTenant()
    .then((tenant) => {
      Cypress.env("C8Y_TENANT", tenant.body.name);
    })
    .then(() => {
      expect(Cypress.env("C8Y_TENANT")).not.be.undefined;
    });
  // or just simply call getTenantId() as this sets the tenant id env variable automatically
  cy.getAuth("admin").getTenantId();
});

Additional frameworks

Other frameworks that might help improve efficiency, quality and reliability of Cypress tests include:

Concepts

To use the custom commands provided in this library across different projects, it comes with some concepts to allow more flexible use.

The most important use case has been accessing credentials for authentication, as probably every project uses a different approach to pass credentials into it's tests.

Authentication and credentials

This library supports different ways to configure authentication and credentials in your tests. The getAuth and useAuth commands create or read authentication options from environment and pass or configure it for given commands or the entire test.

Within this library, all commands must use and support authentication based on C8yAuthOptions. C8yAuthOptions are compatible with authentication options used in

Authentication via getAuth and useAuth commands

For accessing authentication credentials, the getAuth() and useAuth() commands are provided. Both accept any arguments and create, if possible, a C8yAuthOptions object.

// get auth options from Cypress env variables, test annotation or cy.useAuth()
cy.getAuth();

// user "admin" and password from "admin_password" Cypress env variable
cy.getAuth("admin");

// user and password given as strings
cy.getAuth("admin", "password");

// use C8yAuthOptions (for chaining commands)
cy.getAuth({ user: "admin", password: "password" });

getAuth() and useAuth() support chaining by accepting arguments as previous subjects. All commands requiring authentication should accept C8yAuthOptions object as it's first (previous subject) argument.

// without chaining
cy.getAuth().then((auth) => {
  cy.createUser(auth, "newuser");
});

// with chaining
cy.getAuth().createUser("newuser");

With useAuth() the C8yAuthOptions object will be available for all commands within the scope of the test. Use if there is more than one command requiring authentication or if not all commands in a chain yield auth options.

cy.useAuth("admin");

cy.deleteUser("newuser");
cy.createUser({
  userName: "newuser",
  password: "newpassword",
  email: "newuser@example.com",
  displayName: "New User",
});
cy.getApplicationsByName("OEE").subscribeApplications("newuser");

// using getAuth() to pass auth options into login will override the
// authentication options configured via cy.useAuth()
cy.getAuth("newuser").login();

Authentication via test case annotations

Instead of calling useAuth(), it is also possible to annotate the test with authentication options.

it(
  "my test requiring authentication",
  { auth: { user: "myadmin", password: "mypassword" } },
  () => {
    // commands will use auth passed from annotation
  }
);

it("another test requiring authentication", { auth: "myadmin" }, () => {
  // commands will use auth from annotation with password from env variable
});

Authentication via environment variables

To provide authentication options into all tests, use C8Y_USERNAME and C8Y_PASSWORD env variables. Set env variables in your tests or use one of the ways descibed in Cypress documentation.

Example for setting environment variables in your tests:

Cypress.env("C8Y_USERNAME", "admin");
Cypress.env("C8Y_PASSWORD", "password");

Passing authentication to cy.request

With import "cumulocity-cypress/lib/commands/request", it is also possible to add authentication support to cy.request() command. If enabled, cy.request() will use authentication from environment, useAuth() and test case auth annotation. As this feature is considered experimental, it is not automatically imported.

Note: chaining authentication into cy.request() is not supported as cy.request() does not support previous subject and always is a parent in the Cypress chain.

Note: Add the import before other imports (not related to this library). This is required in case cy.request() is overwritten. If any cy.overwrite("request", ...) is called after the import, cy.request() will not automatically use the authentication.

it(
  "use request with authentication from test annotation",
  { auth: "admin" },
  function () {
    cy.request({
      method: "GET",
      url: "/path/to/some/resource",
    }).then((response) => {
      // do something
    });
  }
);

// same as
it("standard request authentication", function () {
  cy.request({
    method: "GET",
    url: "/path/to/some/resource",
    auth: { user: "admin", password: "password" },
  }).then((response) => {
    // do something
  });
});

Chaining of commands

Custom commands provided by this library support chaining. This means commands accept previousSubject and yield it's result for next command in the chain.

Instead of having one command with a lot of arguments, chaining allows splitting into multiple commands.

cy.getAuth("admin", "password").login();
cy.wrap("admin").getAuth().login();

c8y/client and Web SDK types

In general, all custom commands use c8y/client type definitions working with Cumulocity API.

To interact with Cumulocity REST endpoints, cy.c8yclient custom command is provided. cy.c8yclient mimics cy.request to easily exchange or replace cy.request within your tests. For compatibility, the yielded result of cy.c8yclient is a Cypress.Response<T> (as used by cy.request) to make all assertions work as expected for cy.request and cy.c8yclient.

See API and Integration Testing for more information.

Recording of requests and responses

cumulocity-cypress allows to record requests and responses when running e2e, component or API tests. This recordings can be used to match against or mock requests and responses in following runs and to share or document the tested component's use of APIs with other teams or projects.

See API and Integration Testing for more information.

Component testing

cumulocity-cypress provides fully configured cy.mount command for testing Cumulocity Angular components. For general information on component testing in Cypress, see Component Testing.

The cy.mount command comes with capabilities to easily record and mock API requests and responses for component tests. For recording, cy.mount allows running component tests against a configured C8Y_BASEURL and record responses for mocking.

Note: Configuration of baseUrl via C8Y_BASEURL environment variable is required as Cypress component tests do not allow configuration of a baseUrl. Component tests are expected to run using mocked data.

Development

Debugging

Debugging Cypress tests is tricky. To help debugging custom commands, this library comes with needed setup for debugging in Cypress.

Console log debugging

All custom commands of this library are logged within the Command Log of the Cypress App. By clicking the logged command in Command Log of Cypress App, extra information are printed to the console for debugging. Extra information should include at least subject as well as the value yielded by the command. Every command can add any additional information.

Use consoleProps object to pass additional information as in the following example.

// get $args from arguments passed to the command
const [auth, userOptions] = $args;
const consoleProps = {
  // include authentication and user options passed as arguments
  auth: auth,
  userOptions: userOptions,
};

Cypress.log({
  name: "createUser",
  // custom message (title) shown in command log
  message: userOptions.userName,
  consoleProps: () => consoleProps,
});

// do something

cy.request({
  method: "POST",
  url: "/user/" + tenant.name + "/users",
  auth: auth,
  body: userOptions,
}).should((response) => {
  // add more details to console props
  consoleProps.response = response;
});

When adding extra information to the log, keep overall object size in mind. You might run out of memory in case of extensive logging with many large command log entries.

See Get console log for commands from Cypress documentation for more details.

Debugging in Visual Studio Code

Debugging in Visual Studio Code is not very straight forward, but after all it is or should be possible. The project does contain the launch configuration Attach to Chrome, wich requires the Cypress app to be started with npm run test:open.

Once Cypress App has been started, select run and debug Attach to Chrome launch configuration and restart your test(s) in the Cypress App using Rerun all tests.

Create breakpoints for Cypress commands using debugger statement.

it.only("debugging test", () => {
  debugger;
  // to debug getCurrentTenant(), place another debugger statement
  // in the implementation of getCurrentTenant()
  cy.getAuth("admin")
    .getCurrentTenant()
    .then((tenant) => {
      // to debug result of getCurrentTenant, place debugger in then()
      debugger;
      expect(tenant.name).to.equal("mytenant");
      expect(Cypress.env("C8Y_TENANT")).to.deep.equal({ name: "mytenant" });
    });
});

For more information see Debug just like you always do in the official Cypress documentation.

Testing

Cypress is used for testing commands. All tests are located in test/cypress folder. If needed, add HTML fixtures in test/cypress/app/ folder.

Run tests using

npm run cypress

or with opening the Cypress console

npm run cypress:open

Jest unit tests are located in src/ folder as *.spec.ts files. Run jest tests using

npm run jest

Test access of DOM elements

tbd.

Test requests

Testing requests and the processing of it's responses a set of utilities is provided by this library.

// can be called in beforeEach() hook
initRequestStub();

stubResponse<ICurrentTenant>({
  isOkStatusCode: true,
  status: 200,
  body: { name: "mytenant" },
});

cy.getAuth("admin")
  .getCurrentTenant()
  .then((tenant) => {
    expectHttpRequest({
      url: url(`/tenant/currentTenant`),
      method: "GET",
      auth: { user: "admin", password: "password" },
    });
    expect(tenant.name).to.equal("mytenant");
    expect(Cypress.env("C8Y_TENANT")).to.deep.equal({ name: "mytenant" });
  });

Test interceptions

Interceptions are a very important concept to test with stubbed network responses. If custom commands use interceptions, it can be easily triggered using JQueryStatic provided by Cypress.

$.get(url(`/tenant/currentTenant.json`));

If interceptions do not just stub a response, but modify the response from server, mock the service with fixtures in app folder. You might need to append an extension to the endpoint to get the right content type however.

const { $ } = Cypress;

cy.disableGainsight()
  .as("interception")
  .then(() => {
    return $.get(url(`/tenant/currentTenant.json`));
  })
  .then((response) => {
    expect(response.customProperties.gainsightEnabled).to.eq(false);
  })
  .wait("@interception");

Useful links

๐Ÿ“˜ Explore the Knowledge Base
Dive into a wealth of Cumulocity IoT tutorials and articles in our Tech Community Knowledge Base.

๐Ÿ’ก Get Expert Answers
Stuck or just curious? Ask the Cumulocity IoT experts directly on our Forum.

๐Ÿš€ Try Cumulocity IoT
See Cumulocity IoT in action with a Free Trial.

โœ๏ธ Share Your Feedback
Your input drives our innovation. If you find a bug, please create an issue in the repository. If youโ€™d like to share your ideas or feedback, please post them here.

More to discover

Disclaimer

These tools are provided as-is and without warranty or support. They do not constitute part of the Software AG product suite. Users are free to use, fork and modify them, subject to the license agreement. While Software AG welcomes contributions, we cannot guarantee to include every contribution in the master project.