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.
- Overview of commands
- Screenshot automation
- Installation and setup
- Additional frameworks
- Concepts
- Development
- Useful links
- Disclaimer
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
andgetTenantId
createUser
anddeleteUser
assignUserRoles
andclearUserRoles
getSystemVersion
andgetShellVersion
Component testing
mount
Integration and API testing related commands
c8yclient
,c8yclientf
c8ymatch
retryRequest
request
See Integration and API testing for more information.
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.
Add dependency to your package.json and install via npm or yarn.
npm install --save-dev cumulocity-cypress
or
yarn add -D cumulocity-cypress
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
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;
},
},
});
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";
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();
});
Other frameworks that might help improve efficiency, quality and reliability of Cypress tests include:
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.
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
cy.request()
as defined in HTPP AuthenticationICredentials
as defined byc8y/client
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();
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
});
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");
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 anycy.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
});
});
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();
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.
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.
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.
Debugging Cypress tests is tricky. To help debugging custom commands, this library comes with needed setup for debugging in Cypress.
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 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.
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
tbd.
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" });
});
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");
๐ 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
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.