Photo by Jakub Żerdzicki on Unsplash
Scaling Micro-Frontend Reliability: Advanced Techniques in Version Management and Automated Testing
Introduction
This article is a follow-up to The power of micro frontends – How to dynamically extend Cumulocity IoT Frontends. Since the original publication, significant changes have occurred, with an increasing number of plugins being created by both Cumulocity teams and the community. As the use of micro frontends within the Cumulocity platform grew, new challenges emerged. One of the most frequently mentioned issues in the micro frontend space is dependency version incompatibility. We’ve addressed this challenge using a set of metadata called the version matrix and Cypress tests. But first, let’s look at what’s changed…
What has changed since the first micro frontend article?
A major update to our Web SDK was the deprecation of the @c8y/cli
package. It has been replaced by the @c8y/devkit
package, which now serves as a builder for ng-cli. In simpler terms, instead of handling all commands through our own CLI, we now extend Angular CLI. This means you don’t call @c8y/devkit
directly; instead, all commands start with ng <command>
. For more information about ng-cli, check out the Quickstart section.
Another change is that you no longer declare your exports in package.json
. Instead, they are now defined in cumulocity.config.ts
(which is later compiled to cumulocity.json
).
If you’re ready to build your own plugin, refer to the Microfrontends documentation for detailed, step-by-step guidance on creating a plugin.
Version matrix
Introduced in Web SDK version 10.19.0, the version matrix is a part of the plugin designed to ensure compatibility between the plugin, Web SDK, and shell API. To use it, simply add it to your cumulocity.config.ts
file.
Here’s an example of how a version matrix might look (content of cumulocity.config.ts
):
export default {
runTime: {
...
versioningMatrix: {
'2.1.6': {
sdk: '>=1016.0.0 <1018.0.0',
api: '>=1016.0.0 <1018.0.0',
},
'3.2.1': {
sdk: '>=1018.0.0',
api: '>=1018.0.0',
}
},
},
buildTime: {
...
}
} as const satisfies EnvironmentOptions;
The top-level keys (2.1.6
, 3.2.1
) represent the plugin versions, while the sdk
and api
values specify semver ranges of compatible Web SDK and API versions, respectively.
To illustrate how the version matrix works, let’s try installing version 3.2.1 of a plugin with the above matrix into Cockpit version 1017.0.543. You’ll receive this warning:
As you can see, you can still install the plugin, but you’re warned that versions might be incompatible. Some features might not work, or the plugin could fail to load and potentially break your app. This warning won’t appear for Cockpit version 1018.0.0 or higher.
E2E testing plugins against shell
While the versioning matrix works almost effortlessly when properly declared, how do we determine which versions are compatible with our plugin? Manual testing is always an option, but it’s time-consuming and prone to human error.
This concern led us to develop a solution that informs us about incompatibilities between our plugin and the shell app.
Cypress to the rescue!
The most effective way to ensure our plugin works correctly with a particular shell app (without hiring a new QA specialist) is through end-to-end (E2E) tests. For this type of testing, we use the Cypress library along with cumulocity-cypress and cumulocity-cypress-ctrl packages, developed by our team.
We chose our Cumulocity community plugins repository to introduce this feature. The plan was straightforward - we introduced a new workflow that:
1. Collects shell versions
First, we need to decide which shell versions to test our plugin against. For this purpose, we created the plugins-e2e-setup repository containing a custom Github action collect-shell-versions
. This action provides information about the latest versions for relevant distribution tags. By default, it returns the last 3 yearly release tags (and if there aren’t 3 yearly releases yet, it also includes the 1018.0-lts
tag). Here’s an example result of this action:
[
{ "tag": "y2025-lts", "version": "1020.26.2" },
{ "tag": "y2024-lts", "version": "1018.503.119" },
{ "tag": "1018.0-lts", "version": "1018.0.278" }
]
2. Builds the plugin
An obvious step - we need to build the plugin to run it in the shell in later steps.
3. Gets shell apps, hosts them, and runs E2E tests
Now that we know which shell versions we want to test our plugin against, we need to obtain builds of these apps. In our previously mentioned plugins-e2e-setup repository, we have a second custom Github action - get-shell-app
. This action downloads the build of the requested app (Cockpit
, Administration
, or Device Management
) for the specified version.
With both the shell and plugin apps in hand, we can use cumulocity-cypress-ctrl
to host the apps, run Cypress test suites, and verify if the plugins are usable in the shell apps.
Usage of the workflow
Currently, we run this workflow in two scenarios:
- On each pull request
- Scheduled with CRON for nightly runs
The first use case helps us detect incompatibilities mainly due to changes in the plugin itself.
If tests fail, the actions that can be taken are:
- If tests fail because test suites are out of date - update the tests
- If tests fail because the plugin is not compatible with some shell versions:
- Update the versioning matrix to indicate compatible Web SDK versions and align the workflow to test against relevant shell versions
- Or review the changes and select a different approach to ensure plugin compatibility with the shell
While the first use case is mostly relevant to recent changes in the plugin, the second use case targets changes in the shell app. Even if a new plugin version hasn’t been released for days or weeks, the workflow is triggered every night, and tests are run against current versions of the shell app.
Thanks to these scheduled runs, we can receive notifications if a new version of Cockpit is not compatible with our plugin, allowing us to take the actions mentioned above.
Of course, it doesn’t make sense to test the same version of the plugin with the same version of the shell repeatedly. That’s why our CRON job creates a cache of test results, so if no new version of the plugin or shell is released, the test case can be skipped.
The power of cumulocity-cypress-ctrl
You might wonder why we use the cumulocity-cypress-ctrl
package instead of simply using the http-server
package to host the shell and plugin, letting Cypress handle running test suites. There are several compelling reasons. Read full topic here to find out what they are.