UNPKG

cumulocity-cypress

Version:
1,371 lines (1,357 loc) 287 kB
'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;