cumulocity-cypress
Version:
Cypress commands for Cumulocity IoT
1,371 lines (1,357 loc) • 287 kB
JavaScript
'use strict';
var require$$3 = require('@c8y/client');
var _$i = require('lodash');
var url = require('../url-Ka9JSKx_.js');
var util = require('../util-C7wrzo9A.js');
var semver = require('semver');
var setCookieParser = require('set-cookie-parser');
var libCookie = require('cookie');
var localeDe = require('@angular/common/locales/de');
var localeEn = require('@angular/common/locales/en-GB');
var datefns = require('date-fns');
var dateFnsDe = require('date-fns/locale/de');
var dateFnsEnGB = require('date-fns/locale/en-GB');
var util$1 = require('cumulocity-cypress/shared/util');
require('cross-fetch');
require('cypress-file-upload');
var c8yscrn_runnerHelper = require('./runner-helper.js');
require('ajv');
require('ajv-formats');
require('ajv/lib/refs/json-schema-draft-06.json');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var ___namespace = /*#__PURE__*/_interopNamespaceDefault(_$i);
var semver__namespace = /*#__PURE__*/_interopNamespaceDefault(semver);
var setCookieParser__namespace = /*#__PURE__*/_interopNamespaceDefault(setCookieParser);
var libCookie__namespace = /*#__PURE__*/_interopNamespaceDefault(libCookie);
var datefns__namespace = /*#__PURE__*/_interopNamespaceDefault(datefns);
var dateFnsDe__namespace = /*#__PURE__*/_interopNamespaceDefault(dateFnsDe);
var dateFnsEnGB__namespace = /*#__PURE__*/_interopNamespaceDefault(dateFnsEnGB);
/// <reference types="cypress" />
const C8yPactAuthObjectKeys = [
"userAlias",
"user",
"type",
];
/**
* Checks if the given object is a C8yAuthOptions.
*
* @param obj The object to check.
* @param options Options to check for additional properties.
* @returns True if the object is a C8yAuthOptions, false otherwise.
*/
function isAuthOptions(obj) {
return (_$i.isObjectLike(obj) &&
(("user" in obj && "password" in obj) || "token" in obj));
}
// new function to convert C8yAuthOptions to IAuthentication
function toC8yAuthentication(obj) {
if (!obj || !_$i.isObjectLike(obj)) {
return undefined;
}
if (_$i.get(obj, "getFetchOptions")) {
return obj;
}
if (!isAuthOptions(obj)) {
return undefined;
}
if (obj.token) {
return new require$$3.BearerAuth(obj.token);
}
else if (obj.user && obj.password) {
return new require$$3.BasicAuth({
user: obj.user,
password: obj.password,
tenant: obj.tenant,
});
}
return undefined;
}
// map from case insensitive auth type to C8yAuthOptionType
function getAuthType(auth) {
const type = _$i.isString(auth)
? auth.toLowerCase()
: auth?.type?.toLowerCase();
if (type === "bearerauth") {
return "BearerAuth";
}
if (type === "basicauth") {
return "BasicAuth";
}
if (type === "cookieauth") {
return "CookieAuth";
}
return undefined;
}
function hasAuthentication(client) {
if (!client)
return false;
const fetchClient = _$i.get(client, "_client.core") ?? _$i.get(client, "core") ?? client;
const getFetchOptionsFn = _$i.get(fetchClient, "getFetchOptions");
if (_$i.isFunction(getFetchOptionsFn)) {
const options = getFetchOptionsFn.apply(fetchClient);
if (!options)
return false;
if (util.get_i(options, "headers.X-XSRF-TOKEN"))
return true;
if (util.get_i(options, "headers.authorization"))
return true;
}
if (_$i.get(fetchClient, "_auth"))
return true;
return false;
}
function toPactAuthObject(obj) {
return _$i.pick(obj, C8yPactAuthObjectKeys);
}
function isPactAuthObject(obj) {
return (_$i.isObjectLike(obj) &&
("user" in obj || "token" in obj) &&
("userAlias" in obj || "type" in obj || "token" in obj) &&
Object.keys(obj).every((key) => ["token", ...C8yPactAuthObjectKeys].includes(key)));
}
function getAuthOptionsFromEnv(env) {
if (env == null || !_$i.isObjectLike(env)) {
return undefined;
}
// check first environment variables
const jwtToken = env["C8Y_TOKEN"];
let tokenAuth = undefined;
try {
const authFromToken = getAuthOptionsFromJWT(jwtToken);
if (authFromToken) {
tokenAuth = authWithTenant(env, authFromToken);
}
}
catch {
// ignore errors from extractTokensFromJWT
// this is expected if the token is not a valid JWT
}
const user = env[`C8Y_USERNAME`] ?? env[`C8Y_USER`];
const password = env[`C8Y_PASSWORD`];
let basicAuth = undefined;
if (!_$i.isEmpty(user) && !_$i.isEmpty(password)) {
basicAuth = authWithTenant(env, {
user,
password,
});
}
if (!tokenAuth && !basicAuth) {
return undefined;
}
return { ...(basicAuth ?? {}), ...(tokenAuth ?? {}) };
}
function authWithTenant(env, options) {
if (env == null || !_$i.isObjectLike(env)) {
return options;
}
const tenant = env[`C8Y_TENANT`];
if (tenant && !options?.tenant) {
_$i.extend(options, { tenant });
}
return options;
}
function getAuthOptionsFromBasicAuthHeader(authHeader) {
if (!authHeader ||
!_$i.isString(authHeader) ||
!authHeader.startsWith("Basic ")) {
return undefined;
}
const base64Credentials = authHeader.slice("Basic ".length);
const credentials = decodeBase64(base64Credentials);
const components = credentials.split(":");
if (!components || components.length < 2) {
return undefined;
}
return { user: components[0], password: components.slice(1).join(":") };
}
/**
* Extracts the authentication options from a JWT token.
* @param jwtToken The JWT token to extract the authentication options from.
* @returns The extracted authentication options.
*/
function getAuthOptionsFromJWT(jwtToken) {
try {
const payload = JSON.parse(atob(jwtToken.split(".")[1]));
// Remove all characters not valid in JWT tokens (base64url: A-Z, a-z, 0-9, -, _, .)
const cleanedToken = jwtToken?.replace(/[^A-Za-z0-9\-_.]/g, "");
return {
token: cleanedToken,
xsrfToken: payload.xsrfToken,
tenant: payload.ten,
user: payload.sub,
baseUrl: url.normalizeBaseUrl(payload.aud ?? payload.iss),
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to decode JWT token: ${message}`);
}
}
/**
* Extracts the tenant from the basic auth object.
* @param auth The basic auth object containing the user property.
* @returns The tenant or undefined if not found.
*/
function tenantFromBasicAuth(auth) {
if (_$i.isString(auth)) {
auth = { user: auth };
}
if (!auth || !_$i.isObjectLike(auth) || !auth.user)
return undefined;
const components = auth.user.split("/");
if (!components ||
components.length < 2 ||
_$i.isEmpty(components[1]) ||
_$i.isEmpty(components[0]))
return undefined;
return components[0];
}
function decodeBase64(base64) {
if (!base64)
return "";
let decoded;
if (typeof Buffer !== "undefined") {
decoded = Buffer.from(base64, "base64").toString("utf-8");
}
else {
decoded = atob(base64);
}
return decoded;
}
/// <reference types="cypress" />
// workaround for lodash import in Cypress nodejs typescript runtime and browser
const _$h = _$i || ___namespace;
const C8yPactModeValues = [
"record",
"recording",
"apply",
"forward",
"disabled",
"mock",
];
const C8yPactRecordingModeValues = [
"refresh",
"append",
"new",
"replace",
];
/**
* Creates an C8yPactID for a given string or array of strings.
* @param value The string or array of strings to convert to a pact id.
* @returns The pact id.
*/
function pactId(value) {
let result = "";
const suiteSeparator = "__";
const normalize = (value) => value
.split(suiteSeparator)
.map((v) => _$h.words(_$h.deburr(v), /[a-zA-Z0-9_-]+/g).join("_"))
.join(suiteSeparator);
if (value != null && _$h.isArray(value)) {
result = value.map((v) => normalize(v)).join(suiteSeparator);
}
else if (value != null && _$h.isString(value)) {
result = normalize(value);
}
if (result == null || _$h.isEmpty(result)) {
return !value ? value : undefined;
}
return result;
}
/**
* Validate the given pact mode. Throws an error if the mode is not supported
* or undefined.
* @param mode The pact mode to validate.
*/
function validatePactMode(mode) {
if (mode != null) {
const values = Object.values(C8yPactModeValues);
if (!_$h.isString(mode) ||
_$h.isEmpty(mode) ||
!values.includes(mode.toLowerCase())) {
const error = new Error(`Unsupported pact mode: "${mode}". Supported values are: ${values.join(", ")} or undefined.`);
error.name = "C8yPactError";
throw error;
}
}
}
/**
* Validate the given pact recording mode. Throws an error if the mode is not supported
* or undefined.
* @param mode The pact recording mode to validate.
*/
function validatePactRecordingMode(mode) {
if (mode != null) {
const keys = Object.values(C8yPactRecordingModeValues);
if (!_$h.isString(mode) ||
_$h.isEmpty(mode) ||
!keys.includes(mode.toLowerCase())) {
const error = new Error(`Unsupported recording mode: "${mode}". Supported values are: ${keys.join(", ")} or undefined.`);
error.name = "C8yPactError";
throw error;
}
}
}
/**
* Checks if the given object is a C8yPact. This also includes checking
* all records to be valid C8yPactRecord instances.
*
* @param obj The object to check.
* @returns True if the object is a C8yPact, false otherwise.
*/
function isPact(obj) {
return (_$h.isObjectLike(obj) &&
"info" in obj &&
_$h.isObjectLike(_$h.get(obj, "info")) &&
"records" in obj &&
_$h.isArray(_$h.get(obj, "records")) &&
_$h.every(_$h.get(obj, "records"), isPactRecord) &&
_$h.isFunction(_$h.get(obj, "nextRecord")) &&
_$h.isFunction(_$h.get(obj, "nextRecordMatchingRequest")) &&
_$h.isFunction(_$h.get(obj, "appendRecord")) &&
_$h.isFunction(_$h.get(obj, "replaceRecord")));
}
/**
* Checks if the given object is a C8yPactRecord.
*
* @param obj The object to check.
* @returns True if the object is a C8yPactRecord, false otherwise.
*/
function isPactRecord(obj) {
return (_$h.isObjectLike(obj) &&
"request" in obj &&
_$h.isObjectLike(_$h.get(obj, "request")) &&
"response" in obj &&
_$h.isObjectLike(_$h.get(obj, "response")) &&
_$h.isFunction(_$h.get(obj, "toCypressResponse")));
}
/**
* Checks if the given object is a Cypress.Response.
*
* @param obj The object to check.
* @returns True if the object is a Cypress.Response, false otherwise.
*/
function isCypressResponse(obj) {
return (_$h.isObjectLike(obj) &&
"body" in obj &&
"status" in obj &&
"headers" in obj &&
"requestHeaders" in obj &&
"duration" in obj &&
"url" in obj &&
"isOkStatusCode" in obj &&
// not a window.Response or Client.FetchResponse
!("ok" in obj || "arrayBuffer" in obj));
}
function isDefined(value) {
return !_$h.isUndefined(value);
}
/**
* Converts a Cypress.Response to a C8yPactRequest.
*/
function toPactRequest(response) {
if (!response)
return response;
const result = _$h.pickBy(_$h.mapKeys(_$h.pick(response, ["url", "method", "requestHeaders", "requestBody"]), (v, k) => {
if (_$h.isEqual(k, "requestHeaders"))
return "headers";
if (_$h.isEqual(k, "requestBody"))
return "body";
return k;
}), isDefined);
if (_$h.isEmpty(result))
return undefined;
return result;
}
/**
* Converts a Cypress.Response to a C8yPactResponse.
*/
function toPactResponse(response) {
if (!response)
return response;
const result = _$h.pickBy(_$h.pick(response, [
"status",
"statusText",
"body",
"headers",
"duration",
"isOkStatusCode",
"allRequestResponses",
"$body",
]), isDefined);
if (_$h.isEmpty(result))
return undefined;
return result;
}
/**
* Returns the value of the environment variable with the given name. The function
* tries to find the value in the global `process.env` or `Cypress.env()`. If `env`
* is provided, the function uses the given object as environment.
*
* The function tries to find the value in the following order:
* - `name`
* - `camelCase(name)`
* - `CYPRESS_name`
* - `name.replace(/^C8Y_/i, "")`
* - `CYPRESS_camelCase(name)`
* - `CYPRESS_camelCase(name.replace(/^C8Y_/i, ""))`
*
* @param name The name of the environment variable.
* @param env The environment object to use. Default is `process.env` or `Cypress.env()`
*
* @returns The value of the environment variable or `undefined` if not found.
*/
function getEnvVar(name, env) {
if (!name)
return undefined;
const e = (typeof window !== "undefined" && window.Cypress
? Cypress.env()
: process.env);
function getFromEnv(key) {
return e[key];
}
function getForName(name) {
return getFromEnv(name) || getFromEnv(`CYPRESS_${name}`);
}
const plainName = name.replace(/^C8Y_/i, "");
const camelCasedName = _$h.camelCase(name).replace(/^c8Y/i, "c8y");
const camelCasedPlainName = _$h.camelCase(plainName);
return (getForName(name) ||
getForName(camelCasedName) ||
getForName(plainName) ||
getForName(camelCasedPlainName));
}
function getCreatedObjectId(response) {
let newId = response?.body?.id;
if (newId) {
return newId;
}
else {
const location = util.get_i(response, "headers.location");
if (url.isAbsoluteURL(location)) {
try {
const url = new URL(location);
const pathSegments = url?.pathname.split("/").filter(Boolean);
newId = pathSegments?.pop();
if (newId != null) {
return decodeURIComponent(newId);
}
}
catch {
// do nothing
}
}
}
return undefined;
}
const _$g = _$i || ___namespace;
/**
* Checks if the given version satisfies the requirements provided as an array of semver ranges.
* If no required ranges are provided or range is empty, `true` is returned.
* @param version - The version to check as a string or SemVer object.
* @param requires - The required versions as semver ranges or `null` to allow version without specifying a range.
* @returns `true` if the version satisfies the requirements, `false` otherwise.
*/
function isVersionSatisfyingRequirements(version, requires) {
if (!requires || !_$g.isArrayLike(requires) || _$g.isEmpty(requires))
return true;
if (requires.length === 1 && _$g.first(requires) == null)
return true;
let result = true;
if (version != null) {
const requiredRanges = getRangesSatisfyingVersion(version, requires);
result = !_$g.isEmpty(requiredRanges);
}
else {
// null is a special placeholder to mark the test to be executed if NO system version
// is configured. Used for example for mocked tests with cy.intercept.
result = requires?.includes(null);
}
return result;
}
/**
* Returns the required semver ranges that are satisfied by the given version.
* @param version - The version to check as a string or SemVer object.
* @param requires - The required versions as semver ranges or `null` to allow version without specifying a range.
* @returns The ranges that are satisfied by the version.
*/
function getRangesSatisfyingVersion(version, requires) {
if (version == null || requires == null || _$g.isEmpty(requires)) {
return [];
}
return filterNonNull(requires)
.filter((v) => semver__namespace.satisfies(version, v))
.filter((v) => v != null);
}
/**
* Returns the minimum satisfying version for the given version and required ranges. If there is
* more than one range that is satisfied by the version, the minimum version is returned.
* @param version - The version to check as a string or SemVer object.
* @param ranges - The required versions as semver ranges or `null` to allow version without specifying a range.
* @returns The minimum satisfying version.
*/
function getMinSatisfyingVersion(version, ranges) {
const minVersions = getMinSatisfyingVersions(version, ranges);
return _$g.first(minVersions);
}
/**
* Returns all minimum satisfying versions for the given version and required ranges.
* @param version - The version to check as a string or SemVer object.
* @param ranges - The required versions as semver ranges or `null` to allow version without specifying a range.
* @returns All minimum satisfying versions for the given ranges sorted in ascending order.
*/
function getMinSatisfyingVersions(version, ranges) {
if (!version || !ranges || !_$g.isString(version) || !_$g.isArray(ranges)) {
return [];
}
if (filterNonNull(ranges).length === 0) {
return [];
}
if (_$g.isEmpty(ranges)) {
const v = semver__namespace.coerce(version);
return v ? [v] : [];
}
const minVersions = ranges.reduce((acc, range) => {
if (range != null && _$g.isString(range)) {
const coercedVersion = semver__namespace.coerce(version) ?? version;
if (semver__namespace.satisfies(coercedVersion, range)) {
const v = semver__namespace.minVersion(range);
if (v)
acc.push(v);
}
}
else {
const v = semver__namespace.coerce(version);
if (v)
acc.push(v);
}
return acc;
}, []);
return semver__namespace.sort(minVersions);
}
/**
* Returns the minimized version string for the given version. Trailing `.0` patch versions or
* `.0.0` minor versions and patch versions are omitted. If the version is a prerelease or build version,
* the full version is returned.
* @param version - The version to minimize as a string or SemVer object.
* @returns The minimized version string.
*/
function getMinimizedVersionString(version) {
const semVerObj = _$g.isString(version) ? semver__namespace.parse(version) : version;
if (semVerObj == null)
return undefined;
const props = ["major", "minor", "patch", "prerelease", "build"];
if (!props.every((prop) => prop in semVerObj)) {
return undefined;
}
if (semVerObj.patch === 0 &&
semVerObj.minor === 0 &&
!semVerObj.prerelease.length &&
!semVerObj.build.length) {
return `${semVerObj.major}`;
}
else if (semVerObj.patch === 0 &&
!semVerObj.prerelease.length &&
!semVerObj.build.length) {
return `${semVerObj.major}.${semVerObj.minor}`;
}
else {
return semVerObj.version;
}
}
/**
* Converts the given version to a semver compatible version string. This is for
* example converting `1.2` to `1.2.0`.
* @param version - The version to convert.
* @returns The semver version string.
*/
function toSemverVersion(version) {
if (version == null)
return undefined;
// version could possibly be a number, make sure to always convert to string
const result = semver__namespace.coerce(version.toString());
return result?.toString();
}
function filterNonNull(items) {
return items.filter((item) => item !== null);
}
/// <reference types="cypress" />
const { _: _$f } = Cypress;
/**
* Helper to normalize any given arguments and Cypress `prevSubject`.
*
* When chaining commands, Cypress will pass result from previous subject
* (parent) as first argument. In case of optional `prevSubject` Cypress will pass
* `null` as first argument if previous command did not yield a result value.
*
* Depending on the hierarchy of chained commands, the structure of the
* `prevSubject` argument will be different.
*
* Supported options
* 1. [<prevSubject>, arguments]
* 2. {"<index>": [<prevSubject>, arguments], "<index>": arguments }
*
* Structure of 2) depends on the command chainer.
*/
function normalizedArguments(args) {
if (!args)
return [];
let result = [];
if (_$f.isArray(args)) {
result = args;
if (args[0] != null && _$f.isArray(result[0])) {
const subjects = _$f.flatten(result[0]);
result = subjects.concat(result.slice(1));
}
}
else if (_$f.isObjectLike(args)) {
const values = Object.values(args);
result = _$f.flatten(values[0]).concat(values.slice(1));
}
return _$f.dropWhile(result, (a) => !a);
}
/**
* Get `normalizedArguments` and insert auth options from
* env variables at the beginning of the arguments.
*/
function normalizedArgumentsWithAuth(args) {
if (!args)
return [undefined];
const normalized = normalizedArguments(args);
if (_$f.isEmpty(normalized) ||
(!_$f.isEmpty(normalized) && !isAuthOptions(normalized[0]))) {
const auth = getAuthOptionsFromCypressEnv();
if (auth) {
normalized.unshift(auth);
}
else {
if (!args[0]) {
normalized.unshift(undefined);
}
}
}
return normalized;
}
function normalizedC8yclientArguments(args) {
if (!args)
return [undefined];
const normalized = normalizedArgumentsWithAuth(args);
if (getCookieAuthFromEnv() != null && args[0] == null) {
normalized[0] = undefined;
}
return normalized;
}
function getCookieAuthFromEnv() {
const cookieAuth = new require$$3.CookieAuth();
const token = util.get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN");
if (!token || _$f.isEmpty(token)) {
return undefined;
}
return cookieAuth;
}
function getXsrfToken() {
const cookieAuth = new require$$3.CookieAuth();
const token = util.get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN");
if (token != null && !_$f.isEmpty(token)) {
return token;
}
return undefined;
}
function getAuthOptionsFromCypressEnv() {
// check window.localStorage for __auth item
const win = cy.state("window");
const authString = win.localStorage.getItem("__auth");
if (authString && _$f.isString(authString) && !_$f.isEmpty(authString)) {
const authObj = getAuthOptionsFromArgs(JSON.parse(authString));
if (isAuthOptions(authObj)) {
return authObj;
}
}
// check auth options from test case annotation
// configured via it("...", {auth: {...}}, ...)
let auth = getAuthOptionsFromArgs(Cypress.config().auth);
if (isAuthOptions(auth)) {
return auth;
}
auth = getAuthOptionsFromSessionStorage();
if (auth) {
return auth;
}
return getAuthOptionsFromEnv(Cypress.env());
}
function getAuthOptions(...args) {
if (!args || !args.length || (args[0] == null && args.length === 1)) {
return getAuthOptionsFromCypressEnv();
}
// first args are null for every { prevSubject: option } command in the
// call hierarchy. remove all null args from the beginning.
if (args[0] == null) {
args = _$f.dropWhile(args, (a) => !a);
}
else if (_$f.isArray(args[0])) {
args = _$f.flatten(args[0]);
}
const auth = getAuthOptionsFromArgs(...args);
if (isAuthOptions(auth)) {
return authWithTenant(Cypress.env(), auth);
}
else if (args.length === 1 && _$f.isString(args[0])) {
return undefined;
}
return getAuthOptionsFromCypressEnv();
}
function userAliasFromArgs(...args) {
if (!args || !args.length)
return undefined;
if (args[0] == null) {
args = _$f.dropWhile(args, (a) => !a);
}
else if (_$f.isArray(args[0])) {
args = _$f.flatten(args[0]);
}
return args.length === 1 && _$f.isString(args[0]) ? args[0] : undefined;
}
function getAuthOptionsFromArgs(...args) {
// do not call getAuthOptionsFromEnv() in here!
const commonFields = [
"password",
"tenant",
"userAlias",
"type",
"token",
"xsrfToken",
];
// getAuthOptions("admin")
// return envs admin_token (preferred) or admin_username | admin, admin_password
let tokenAuth = undefined;
let basicAuth = undefined;
if (!_$f.isEmpty(args) && _$f.isString(args[0])) {
const token = Cypress.env(`${args[0]}_token`);
if (token) {
tokenAuth = authWithTenant(Cypress.env(), {
token,
userAlias: args[0],
});
}
const user = Cypress.env(`${args[0]}_username`) || args[0];
const password = Cypress.env(`${args[0]}_password`);
if (user && password) {
basicAuth = authWithTenant(Cypress.env(), {
user,
password,
userAlias: args[0],
});
}
}
if (tokenAuth || basicAuth) {
return { ...(tokenAuth ?? {}), ...(basicAuth ?? {}) };
}
// getAuthOptions({user: "abc", password: "abc"}, ...)
if (!_$f.isEmpty(args) && _$f.isObjectLike(args[0])) {
if (isAuthOptions(args[0])) {
return authWithTenant(Cypress.env(), _$f.pick(args[0], ["user", ...commonFields]));
}
// getAuthOptions({userAlias: "abc"}, ...)
if (args[0].userAlias) {
const token = Cypress.env(`${args[0].userAlias}_token`);
if (token) {
return authWithTenant(Cypress.env(), {
..._$f.pick(args[0], commonFields),
token,
});
}
const user = Cypress.env(`${args[0].userAlias}_username`) || args[0].userAlias;
const password = Cypress.env(`${args[0].userAlias}_password`);
if (user && password) {
return authWithTenant(Cypress.env(), {
..._$f.pick(args[0], commonFields),
user,
password,
});
}
}
// getAuthOptions({user: "abc", password: "abc"}, ...)
if (args[0].username && args[0].password) {
const auth = _$f.pick(args[0], ["username", "tenantId", ...commonFields]);
delete Object.assign(auth, { user: auth.username })["username"];
if (auth.tenantId && !auth.tenant) {
delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"];
}
return authWithTenant(Cypress.env(), auth);
}
// from IUser: getAuthOptions({userName: "abc", password: "abc"}, ...)
if (args[0].userName && args[0].password) {
const auth = _$f.pick(args[0], ["userName", "tenantId", ...commonFields]);
delete Object.assign(auth, { user: auth.userName })["userName"];
if (auth.tenantId && !auth.tenant) {
delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"];
}
return authWithTenant(Cypress.env(), auth);
}
// from IUser: getAuthOptions({userName: "abc", password: "abc"}, ...)
if (args[0].userName && args[0].password) {
const auth = _$f.pick(args[0], ["userName", "tenantId", ...commonFields]);
delete Object.assign(auth, { user: auth.userName })["userName"];
if (auth.tenantId && !auth.tenant) {
delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"];
}
return authWithTenant(Cypress.env(), auth);
}
}
// getAuthOptions("abc", "abc")
if (args.length >= 2 && _$f.isString(args[0]) && _$f.isString(args[1])) {
return authWithTenant(Cypress.env(), {
user: args[0],
password: args[1],
});
}
return undefined;
}
/**
* Gets and implementation of IAuthentication from the given auth options.
*/
function getC8yClientAuthentication(auth) {
let authOptions;
let result;
if (auth) {
if (_$f.isString(auth)) {
authOptions = getAuthOptions(auth);
}
else if (_$f.isObjectLike(auth)) {
if ("logout" in auth) {
result = auth;
}
else {
authOptions = auth;
}
}
}
if (!result) {
const jwtToken = authOptions?.token;
const xsrfToken = getXsrfToken();
if (jwtToken && !_$f.isEmpty(jwtToken.trim())) {
result = new require$$3.BearerAuth(jwtToken);
}
else if (xsrfToken && !_$f.isEmpty(xsrfToken.trim())) {
result = new require$$3.CookieAuth();
}
else {
result = new require$$3.BasicAuth(authOptions);
}
}
return result;
}
// check session storage for Bearer token
// used by c8y/client login service
function getAuthOptionsFromSessionStorage() {
const win = cy.state("window");
const token = win.sessionStorage.getItem(require$$3.BearerAuthFromSessionStorage.sessionStorageKey);
if (token != null && !_$f.isEmpty(token)) {
const a = getAuthOptionsFromJWT(token);
if (a && isAuthOptions(a)) {
a.type = "BearerAuth";
return a;
}
}
return undefined;
}
function getSystemVersionFromEnv() {
let result = toSemverVersion(Cypress.env(`C8Y_SYSTEM_VERSION`) || Cypress.env(`C8Y_VERSION`));
if (result == null &&
Cypress.c8ypact?.isEnabled() === true &&
Cypress.c8ypact.mode() === "mock") {
const pactVersion = Cypress.c8ypact.current?.info.version?.system;
if (pactVersion) {
result = toSemverVersion(pactVersion);
}
}
return result;
}
function getShellVersionFromEnv() {
return Cypress.env(`C8Y_SHELL_VERSION`);
}
/**
* Tries to get the base URL from environment variables. The following
* environment variables are checked in order:
* - C8Y_BASEURL
* - C8Y_BASE_URL
* - C8Y_HOST
*
* URLs without a protocol will have HTTPS added automatically.
*
* @returns Base URL from environment variables with HTTPS protocol.
*/
function getBaseUrlFromEnv() {
const baseUrl = getEnvVar("C8Y_BASEURL") ||
getEnvVar("C8Y_BASE_URL") ||
getEnvVar("C8Y_HOST") ||
Cypress.config().baseUrl ||
undefined;
return url.normalizeBaseUrl(baseUrl);
}
function storeClient(client) {
cy.state("c8yclient", client);
}
function restoreClient() {
return cy.state("c8yclient");
}
function resetClient() {
cy.state("c8yclient", undefined);
}
function throwError(message) {
const newErr = new Error(message);
// newErr.name = "CypressError";
const capture = Error.captureStackTrace;
if (typeof capture === "function") {
// Exclude this helper from the stack so the call site is shown first
capture(newErr, throwError);
}
else if (newErr.stack) {
// Fallback: remove frames that reference this helper
const lines = newErr.stack.split("\n");
newErr.stack = lines
.filter((l) => !/\s+at\s+throwError\s*\(/.test(l))
.join("\n");
}
throw newErr;
}
if (!Cypress.c8ypact) {
Cypress.c8ypact = {
mode: () => "disabled",
recordingMode: () => "refresh",
current: null,
getCurrentTestId: () => "-",
isRecordingEnabled: () => false,
isMockingEnabled: () => false,
savePact: () => new Promise((resolve) => resolve()),
isEnabled: () => false,
matcher: undefined,
pactRunner: undefined,
schemaMatcher: undefined,
debugLog: false,
preprocessor: undefined,
config: {},
getConfigValue: (key, defaultValue) => defaultValue,
getConfigValues: () => ({}),
loadCurrent: () => cy.wrap(null, { log: false }),
env: () => ({}),
on: {},
createFetchClient: (auth, baseUrl) => new require$$3.FetchClient(getC8yClientAuthentication(auth), baseUrl),
};
}
if (!Cypress.c8yctrl) {
Cypress.c8yctrl = {
mode: () => "disabled",
recordingMode: () => "append",
get current() {
return null;
},
isEnabled: () => false,
isRecordingEnabled: () => false,
isMockingEnabled: () => false,
setCurrent: () => cy.wrap(null),
resetCurrent: () => cy.wrap(null),
url: () => null,
debugLog: false,
};
}
const { _: _$e } = Cypress;
if (Cypress.semver == null) {
Cypress.semver = semver__namespace;
}
beforeEach(function () {
if (Cypress.env("C8Y_IGNORE_REQUIRES_SKIP") == null &&
// backward compatibility
Cypress.env("C8Y_PACT_IGNORE_VERSION_SKIP") == null &&
isSystemVersionSatisfyingCurrentTestRequirements() === false) {
this.skip();
}
});
/**
* Checks if `Cypress.config().requires` matches environment for the current test.
* @returns `true` if the system version satisfies the requirements of the current test, `false` otherwise.
*/
function isSystemVersionSatisfyingCurrentTestRequirements() {
const requires = Cypress.config().requires;
if (requires == null)
return true;
if (_$e.isArrayLike(requires)) {
return isVersionSatisfyingRequirements(getSystemVersionFromEnv(), requires);
}
else {
let systemResult = true;
if (requires.system != null) {
systemResult = isVersionSatisfyingRequirements(getSystemVersionFromEnv(), requires.system);
}
let shellResult = true;
if (requires.shell != null) {
shellResult = isVersionSatisfyingRequirements(getShellVersionFromEnv(), requires.shell);
}
return systemResult && shellResult;
}
}
const getAuthEnvVariables = () => {
const env = Cypress.env();
const filteredKeysAndValues = {};
Object.keys(env).forEach((key) => {
if (key.endsWith("_username") ||
key.endsWith("_password") ||
key.endsWith("_token") ||
key === "C8Y_USERNAME" ||
key === "C8Y_USER" ||
key === "C8Y_PASSWORD" ||
key === "C8Y_TOKEN" ||
key === "C8Y_XSRF_TOKEN" ||
key === "C8Y_AUTHORIZATION") {
filteredKeysAndValues[key] = env[key];
}
});
return filteredKeysAndValues;
};
Cypress.Commands.add("getAuth", { prevSubject: "optional" }, (...args) => {
const auth = authFn("getAuth", args);
return cy.wrap(auth, { log: false });
});
Cypress.Commands.add("useAuth", { prevSubject: "optional" }, (...args) => {
const auth = authFn("useAuth", args);
if (auth != null) {
const win = cy.state("window");
win.localStorage.setItem("__auth", JSON.stringify(auth));
}
resetClient();
return cy.wrap(auth, { log: false });
});
function authFn(fnName, args) {
const auth = getAuthOptions(...args);
const userAlias = userAliasFromArgs(...args);
const consoleProps = {
getauthoptions: auth || null,
arguments: args || null,
env: getAuthEnvVariables() || null,
userAlias: userAlias || null,
};
const logger = Cypress.log({
name: fnName,
message: `${auth?.userAlias ? auth.userAlias + " -> " : ""}${auth ? auth.user : ""}`,
consoleProps: () => consoleProps,
autoEnd: false,
});
if (auth == null && userAlias != null) {
logger.end();
throw new Error(`No authentication found for userAlias ${userAlias}. Configure authentication ` +
`using ${userAlias}_token or ${userAlias}_username and ${userAlias}_password environment variables.`);
}
consoleProps.Yields = auth || null;
logger.end();
return auth;
}
const { _: _$d } = Cypress;
/**
* Default selector to wait for when visiting a page. This selector works for different
* Cumulocity versions.
*/
const C8yVisitDefaultWaitSelector = "c8y-drawer-outlet c8y-app-icon .c8y-icon, c8y-navigator-outlet c8y-app-icon";
Cypress.Commands.add("visitAndWaitForSelector", (url, languageOrOptions, selectorValue, timeoutValue) => {
const DEFAULT_LANGUAGE = "en";
const DEFAULT_TIMEOUT = Cypress.config().pageLoadTimeout || 60000;
const isOptionsObject = (value) => {
return typeof value === "object" && value != null;
};
const options = isOptionsObject(languageOrOptions)
? languageOrOptions
: {
language: languageOrOptions,
selector: selectorValue,
timeout: timeoutValue,
};
const language = options.language ?? DEFAULT_LANGUAGE;
const selector = options.selector ?? C8yVisitDefaultWaitSelector;
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
let remotes = options.remotes ??
Cypress.env("C8Y_SHELL_REMOTES") ??
Cypress.env("C8Y_SHELL_EXTENSION");
if (remotes && typeof remotes === "object") {
remotes = JSON.stringify(remotes);
}
const shell = options.shell ??
Cypress.env("C8Y_SHELL_TARGET") ??
Cypress.env("C8Y_SHELL_NAME");
let forceUrlRemotes = options.forceUrlRemotes ?? Cypress.env("C8Y_SHELL_REMOTES_FORCE");
if (forceUrlRemotes != null && typeof forceUrlRemotes !== "boolean") {
forceUrlRemotes = util.to_boolean(forceUrlRemotes, false);
}
// Build the final URL with shell target if provided
if (shell) {
url = `/apps/${shell}/index.html#/${url}`;
}
// Log command execution details
const consoleProps = {
url,
language,
selector,
timeout,
shell,
remotes,
forceUrlRemotes,
};
Cypress.log({
name: "visitAndWaitForSelector",
message: url + (remotes ? ` ${remotes}` : ""),
consoleProps: () => consoleProps,
});
cy.setLanguage(language);
const qs = remotes || forceUrlRemotes
? {
qs: {
...(remotes != null && { remotes }),
...(forceUrlRemotes != null && { forceUrlRemotes }),
},
}
: undefined;
cy.visit(url, qs);
cy.get(selector, { timeout }).should("be.visible");
});
Cypress.Commands.add("setLanguage", (lang) => {
// in case only some of the entry points have been imported e.g. only `cumulocity-cypress/commands/general`, then `setLocale` might not be defined
if (typeof globalThis.setLocale === "function") {
globalThis.setLocale(lang);
}
Cypress.log({
name: "setLanguage",
message: lang,
});
cy.intercept({
method: "GET",
url: "/inventory/managedObjects?fragmentType=language*",
}, (req) => {
req.continue((res) => {
const languageFragment = req.query.fragmentType.toString();
if (res.body[languageFragment]) {
res.body[languageFragment] = lang;
}
else if (res.body.managedObjects &&
_$d.isArrayLike(res.body.managedObjects)) {
res.body.managedObjects.forEach((mo) => {
if (mo[languageFragment]) {
mo[languageFragment] = lang;
}
});
}
res.send();
});
});
window.localStorage.setItem("c8y_language", lang);
Cypress.on("window:before:load", (window) => {
window.localStorage.setItem("c8y_language", lang);
});
});
Cypress.Commands.add("disableGainsight", () => {
Cypress.log({
name: "disableGainsight",
});
cy.intercept("/tenant/system/options/gainsight/api.key*", (req) => {
req.reply({ statusCode: 404, body: {} });
throw new Error("Intercepted Gainsight API key call, but Gainsight should have been disabled. Failing...");
}).as("GainsightAPIKey");
cy.intercept("/tenant/currentTenant*", (req) => {
req.continue((res) => {
const customProperties = res.body.customProperties || {};
customProperties.gainsightEnabled = false;
res.body.customProperties = customProperties;
res.send();
});
});
});
function normalizeDateParsingWhitespace(value) {
// Angular locale patterns may contain non-breaking or narrow non-breaking spaces.
// Normalizing allows parsing inputs that use regular spaces.
return value.replace(/[\u00A0\u202F]/g, " ");
}
function parseDate(date, format) {
let parsedDate = undefined;
// try to parse as number first, if string is passed it might be converted without format being used
if (_$i.isNumber(date)) {
parsedDate = new Date(date);
}
// parse with format
if (!isValidDate(parsedDate) && _$i.isString(date)) {
const normalizedDate = normalizeDateParsingWhitespace(date);
const normalizedFormat = normalizeDateParsingWhitespace(format);
parsedDate = datefns.parse(normalizedDate, normalizedFormat, new Date());
// if (!isValidDate(parsedDate) && _.isString(date)) {
// parsedDate = new Date(date);
// }
}
if (!isValidDate(parsedDate)) {
parsedDate = undefined;
}
return parsedDate;
}
function isValidDate(date) {
return date != null && !isNaN(date) && _$i.isDate(date);
}
function normalizeLocaleId(localeId) {
return localeId.toLowerCase().replace(/_/g, "-");
}
// default locales to be registered automatically
const { _: _$c } = Cypress;
// https://angular.io/api/common/DatePipe#pre-defined-format-options
// https://github.com/angular/angular/blob/9847085448feff29ac6d51493e224250990c3ff0/packages/common/src/pipes/date_pipe.ts#L58
// not imported from @angular/common to avoid requiring jit at runtime
var FormatWidth;
(function (FormatWidth) {
FormatWidth[FormatWidth["Short"] = 0] = "Short";
FormatWidth[FormatWidth["Medium"] = 1] = "Medium";
FormatWidth[FormatWidth["Long"] = 2] = "Long";
FormatWidth[FormatWidth["Full"] = 3] = "Full";
})(FormatWidth || (FormatWidth = {}));
// Some i18n functions from Angular are used directly in here. This is required to not have Angular in a particular
// version as a dependency of this package. locales must be imported in the tests with the version used in the project.
// See as sources:
// https://github.com/angular/angular/tree/6f5dabe0d25a5660b7c3001041449b4622dd8924/packages/core/src/i18n
// https://github.com/angular/angular/tree/6f5dabe0d25a5660b7c3001041449b4622dd8924/packages/common/src/i18n
// https://github.com/angular/angular/blob/6f5dabe0d25a5660b7c3001041449b4622dd8924/packages/common/src/i18n/locale_data_api.ts
// https://github.com/angular/angular/blob/6f5dabe0d25a5660b7c3001041449b4622dd8924/packages/core/src/i18n/locale_data_api.ts
var NgLocaleDataIndex;
(function (NgLocaleDataIndex) {
NgLocaleDataIndex[NgLocaleDataIndex["LocaleId"] = 0] = "LocaleId";
NgLocaleDataIndex[NgLocaleDataIndex["DayPeriodsFormat"] = 1] = "DayPeriodsFormat";
NgLocaleDataIndex[NgLocaleDataIndex["DayPeriodsStandalone"] = 2] = "DayPeriodsStandalone";
NgLocaleDataIndex[NgLocaleDataIndex["DaysFormat"] = 3] = "DaysFormat";
NgLocaleDataIndex[NgLocaleDataIndex["DaysStandalone"] = 4] = "DaysStandalone";
NgLocaleDataIndex[NgLocaleDataIndex["MonthsFormat"] = 5] = "MonthsFormat";
NgLocaleDataIndex[NgLocaleDataIndex["MonthsStandalone"] = 6] = "MonthsStandalone";
NgLocaleDataIndex[NgLocaleDataIndex["Eras"] = 7] = "Eras";
NgLocaleDataIndex[NgLocaleDataIndex["FirstDayOfWeek"] = 8] = "FirstDayOfWeek";
NgLocaleDataIndex[NgLocaleDataIndex["WeekendRange"] = 9] = "WeekendRange";
NgLocaleDataIndex[NgLocaleDataIndex["DateFormat"] = 10] = "DateFormat";
NgLocaleDataIndex[NgLocaleDataIndex["TimeFormat"] = 11] = "TimeFormat";
NgLocaleDataIndex[NgLocaleDataIndex["DateTimeFormat"] = 12] = "DateTimeFormat";
NgLocaleDataIndex[NgLocaleDataIndex["NumberSymbols"] = 13] = "NumberSymbols";
NgLocaleDataIndex[NgLocaleDataIndex["NumberFormats"] = 14] = "NumberFormats";
NgLocaleDataIndex[NgLocaleDataIndex["CurrencyCode"] = 15] = "CurrencyCode";
NgLocaleDataIndex[NgLocaleDataIndex["CurrencySymbol"] = 16] = "CurrencySymbol";
NgLocaleDataIndex[NgLocaleDataIndex["CurrencyName"] = 17] = "CurrencyName";
NgLocaleDataIndex[NgLocaleDataIndex["Currencies"] = 18] = "Currencies";
NgLocaleDataIndex[NgLocaleDataIndex["Directionality"] = 19] = "Directionality";
NgLocaleDataIndex[NgLocaleDataIndex["PluralCase"] = 20] = "PluralCase";
NgLocaleDataIndex[NgLocaleDataIndex["ExtraData"] = 21] = "ExtraData";
NgLocaleDataIndex[NgLocaleDataIndex["DfnsLocale"] = 22] = "DfnsLocale";
})(NgLocaleDataIndex || (NgLocaleDataIndex = {}));
const LOCALE_DATA = {};
function getNgLocaleId(locale) {
const data = getNgLocale(locale);
return data[NgLocaleDataIndex.LocaleId];
}
/**
* Registers a locale with the given ID and data. Registered locale can be used
* with `cy.setLanguage` to set the locale for date formatting and other
* locale-specific operations in `cy.toDate`, `cy.toISODate`, etc.
* @param c8yLocaleId The Cumulocity locale ID (e.g., "en", "de").
* @param angularLocale The Angular locale data.
* @param dfnsLocale The date-fns locale data (optional).
* @param extraData Additional data to be stored in the locale (optional).
*/
function registerLocale(c8yLocaleId, angularLocale, dfnsLocale = null, extraData = undefined) {
const angularId = normalizeLocaleId(c8yLocaleId);
LOCALE_DATA[angularId] = angularLocale;
if (extraData) {
LOCALE_DATA[angularId][NgLocaleDataIndex.ExtraData] = extraData;
}
LOCALE_DATA[angularId][NgLocaleDataIndex.DfnsLocale] = {
...(typeof dfnsLocale === 'object' && dfnsLocale !== null ? dfnsLocale : {}),
localize: {
...dfnsLocale?.localize,
month: buildLocalizeFn({
values: monthValuesForLocale(angularId),
defaultWidth: "wide",
}),
day: buildLocalizeFn({
values: dayValuesForLocale(angularId),
defaultWidth: "wide",
}),
},
// node_modules/date-fns/locale/en-US/_lib/match/index.js
match: {
...dfnsLocale?.match,
month: buildMatchFn({
matchPatterns: matchMonthPatterns(angularId),
defaultMatchWidth: "wide",
parsePatterns: parseMonthPatterns(angularId),
defaultParseWidth: "any",
}),
day: buildMatchFn({
matchPatterns: matchDayPatterns(angularId),
defaultMatchWidth: "wide",
parsePatterns: parseDayPatterns(angularId),
defaultParseWidth: "any",
}),
},
};
}
/**
* Registers default locales `de` and `en` with their respective
* Angular and date-fns locales for use in tests.
*/
function registerDefaultLocales() {
registerLocale("de",
// @ts-expect-error
!isModule(localeDe) ? localeDe : localeDe.default, dateFnsDe__namespace.default || dateFnsDe__namespace);
registerLocale("en",
// @ts-expect-error
!isModule(localeEn) ? localeEn : localeEn.default, dateFnsEnGB__namespace.default || dateFnsEnGB__namespace);
}
function getNgLocale(localeId) {
const getNgLocaleData = (localeId) => {
const normalizedLocale = normalizeLocaleId(localeId);
if (!(normalizedLocale in LOCALE_DATA)) {
LOCALE_DATA[normalizedLocale] =
// @ts-expect-error
globalThis.ng?.common?.locales?.[normalizedLocale];
}
return LOCALE_DATA[normalizedLocale];
};
const normalizedLocale = normalizeLocaleId(localeId);
let match = getNgLocaleData(normalizedLocale);
if (match) {
return match;
}
// let's try to find a parent locale
const parentLocale = normalizedLocale.split("-")[0];
match = getNgLocaleData(parentLocale);
if (match) {
return match;
}
throw new Error(`Missing locale data for the locale "${localeId}".`);
}
function localizedTimeFormat(localeId = "en", formatWidth = FormatWidth.Short) {
return getLocaleValue(localeId, NgLocaleDataIndex.TimeFormat, formatWidth);
}
function localizedDateFormat(localeId = "en", formatWidth = FormatWidth.Short) {
return getLocaleValue(localeId, NgLocaleDataIndex.DateFormat, formatWidth);
}
function localizedDateTimeFormat(localeId = "en", formatWidth = FormatWidth.Short) {
const fullTime = getLocaleValue(localeId, NgLocaleDataIndex.TimeFormat, formatWidth);
const fullDate = getLocaleValue(localeId, NgLocaleDataIndex.DateFormat, formatWidth);
return formatDateTime(getLocaleValue(localeId, NgLocaleDataIndex.DateTimeFormat, formatWidth), [fullTime, fullDate]);
}
// https://github.com/angular/angular/blob/fe691935091aaf7090864c8111a15f7cc7e53b6c/packages/common/src/i18n/format_date.ts#L201
function formatDateTime(str, opt_values) {
if (opt_values) {
str = str.replace(/\{([^}]+)}/g, function (match, key) {
return opt_values != null && key in opt_values ? opt_values[key] : match;