0.4.0 • Published 4 days ago

cumulocity-cypress v0.4.0

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
4 days ago

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

Component testing

  • mount

Integration and API testing related commands

  • c8yclient, c8yclientf
  • c8ymatch
  • retryRequest

See Integration and API testing for more information.

Installation and setup

Add dependency to your package.json.

npm install cumulocity-cypress --save-dev
yarn add -D cumulocity-cypress

You also need to have @c8y/client installed and make it available within the tested project as cumulocity-cypress defines @c8y/client as a peer-dependency. This is to ensure the version of @c8y/client to be used is the same as in the hosted test project.

Install @c8y/client if needed using

npm install @c8y/client --save-dev
yarn add -D @c8y/client

Configure Cumulocity authentication. Easiest way to configure authentication is to create cypress.env.json file in your project and add all credentials needed for the tests, for example with different permissions or roles.

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

Update your projects e2e.supportFile (e.g. cypress/support/e2e.ts) to make custom commands available to your tests.

Import all commands:

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

Import selected commands:

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

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.

With this, also in the support file of your Cypress project, you can init environment variables as for example with:

before(() => {
  cy.getAuth("admin")
    .getCurrentTenant()
    .then((tenant) => {
      Cypress.env("C8Y_TENANT", tenant.body.name);
      Cypress.env(
        "C8Y_INSTANCE",
        tenant.body.domainName?.split(".").slice(1).join(".")
      );
    })
    .then(() => {
      expect(Cypress.env("C8Y_TENANT")).not.be.undefined;
      expect(Cypress.env("C8Y_INSTANCE")).not.be.undefined;
    });
});

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: in order to work, add the import before any other imports (not related to this library) in your support file. This is required if 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 should support chaining. This means commands need to 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 should 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.

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.

0.4.0

4 days ago

0.3.4

23 days ago

0.3.3

25 days ago

0.3.2

1 month ago

0.3.1

1 month ago

0.3.0

1 month ago

0.2.6

2 months ago

0.2.5

3 months ago

0.2.4

6 months ago

0.2.3

6 months ago