UNPKG

cumulocity-cypress

Version:
1,414 lines (1,400 loc) 214 kB
'use strict'; var client = require('@c8y/client'); var _$j = require('lodash'); var semver = require('semver'); var util = require('../util-CJ8J1x_Y.js'); var localeDe = require('@angular/common/locales/de'); var localeEn = require('@angular/common/locales/en-GB'); var buildLocalizeFn = require('date-fns/locale/_lib/buildLocalizeFn'); var buildMatchFn = require('date-fns/locale/_lib/buildMatchFn'); var datefns = require('date-fns'); var setCookieParser = require('set-cookie-parser'); var libCookie = require('cookie'); var ajv = require('../ajv-qt7Q2OLe.js'); 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(_$j); var semver__namespace = /*#__PURE__*/_interopNamespaceDefault(semver); var datefns__namespace = /*#__PURE__*/_interopNamespaceDefault(datefns); var setCookieParser__namespace = /*#__PURE__*/_interopNamespaceDefault(setCookieParser); var libCookie__namespace = /*#__PURE__*/_interopNamespaceDefault(libCookie); /// <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 _$j.isObjectLike(obj) && "user" in obj && "password" in obj; } function toPactAuthObject(obj) { return _$j.pick(obj, C8yPactAuthObjectKeys); } function isPactAuthObject(obj) { return (_$j.isObjectLike(obj) && "user" in obj && ("userAlias" in obj || "type" in obj) && Object.keys(obj).every((key) => C8yPactAuthObjectKeys.includes(key))); } function getAuthOptionsFromBasicAuthHeader(authHeader) { if (!authHeader || !_$j.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(":") }; } 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 _$i = _$j || ___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) => _$i.words(_$i.deburr(v), /[a-zA-Z0-9_-]+/g).join("_")) .join(suiteSeparator); if (value != null && _$i.isArray(value)) { result = value.map((v) => normalize(v)).join(suiteSeparator); } else if (value != null && _$i.isString(value)) { result = normalize(value); } if (result == null || _$i.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 (!_$i.isString(mode) || _$i.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 (!_$i.isString(mode) || _$i.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 (_$i.isObjectLike(obj) && "info" in obj && _$i.isObjectLike(_$i.get(obj, "info")) && "records" in obj && _$i.isArray(_$i.get(obj, "records")) && _$i.every(_$i.get(obj, "records"), isPactRecord) && _$i.isFunction(_$i.get(obj, "nextRecord")) && _$i.isFunction(_$i.get(obj, "nextRecordMatchingRequest")) && _$i.isFunction(_$i.get(obj, "appendRecord")) && _$i.isFunction(_$i.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 (_$i.isObjectLike(obj) && "request" in obj && _$i.isObjectLike(_$i.get(obj, "request")) && "response" in obj && _$i.isObjectLike(_$i.get(obj, "response")) && _$i.isFunction(_$i.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 (_$i.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)); } /** * Checks if the given object is a C8yPactError. A C8yPactError is an error * with the name "C8yPactError". * * @param error The object to check. * @returns True if the object is a C8yPactError, false otherwise. */ function isPactError(error) { return _$i.isError(error) && _$i.get(error, "name") === "C8yPactError"; } function isDefined(value) { return !_$i.isUndefined(value); } /** * Converts a Cypress.Response to a C8yPactRequest. */ function toPactRequest(response) { if (!response) return response; const result = _$i.pickBy(_$i.mapKeys(_$i.pick(response, ["url", "method", "requestHeaders", "requestBody"]), (v, k) => { if (_$i.isEqual(k, "requestHeaders")) return "headers"; if (_$i.isEqual(k, "requestBody")) return "body"; return k; }), isDefined); if (_$i.isEmpty(result)) return undefined; return result; } /** * Converts a Cypress.Response to a C8yPactResponse. */ function toPactResponse(response) { if (!response) return response; const result = _$i.pickBy(_$i.pick(response, [ "status", "statusText", "body", "headers", "duration", "isOkStatusCode", "allRequestResponses", "$body", ]), isDefined); if (_$i.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 = env || (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 = _$i.camelCase(name).replace(/^c8Y/i, "c8y"); const camelCasedPlainName = _$i.camelCase(plainName); return (getForName(name) || getForName(camelCasedName) || getForName(plainName) || getForName(camelCasedPlainName)); } const _$h = _$j || ___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 || !_$h.isArrayLike(requires) || _$h.isEmpty(requires)) return true; if (requires.length === 1 && _$h.first(requires) == null) return true; let result = true; if (version != null) { const requiredRanges = getRangesSatisfyingVersion(version, requires); result = !_$h.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 || _$h.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 _$h.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 || !_$h.isString(version) || !_$h.isArray(ranges)) { return []; } if (filterNonNull(ranges).length === 0) { return []; } if (_$h.isEmpty(ranges)) { const v = semver__namespace.coerce(version); return v ? [v] : []; } const minVersions = ranges.reduce((acc, range) => { if (range != null && _$h.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 = _$h.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 { _: _$g } = 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 (_$g.isArray(args)) { result = args; if (args[0] != null && _$g.isArray(result[0])) { const subjects = _$g.flatten(result[0]); result = subjects.concat(result.slice(1)); } } else if (_$g.isObjectLike(args)) { const values = Object.values(args); result = _$g.flatten(values[0]).concat(values.slice(1)); } return _$g.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 (_$g.isEmpty(normalized) || (!_$g.isEmpty(normalized) && !isAuthOptions(normalized[0]))) { const auth = getAuthOptionsFromEnv(); 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 client.CookieAuth(); const token = util.get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN"); if (!token || _$g.isEmpty(token)) { return undefined; } return cookieAuth; } function getAuthOptionsFromEnv() { // check window.localStorage for __auth item const win = cy.state("window"); const authString = win.localStorage.getItem("__auth"); if (authString && _$g.isString(authString) && !_$g.isEmpty(authString)) { const authObj = getAuthOptionsFromArgs(JSON.parse(authString)); if (isAuthOptions(authObj)) { return authObj; } } // check auth options from test case annotation // configured via it("...", {auth: {...}}, ...) const auth = getAuthOptionsFromArgs(Cypress.config().auth); if (isAuthOptions(auth)) { return auth; } // check first environment variables const user = Cypress.env(`C8Y_USERNAME`); const password = Cypress.env(`C8Y_PASSWORD`); if (!_$g.isEmpty(user) && !_$g.isEmpty(password)) { return authWithTenant({ user, password, }); } return undefined; } function getAuthOptions(...args) { if (!args || !args.length || (args[0] == null && args.length === 1)) { return getAuthOptionsFromEnv(); } // 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 = _$g.dropWhile(args, (a) => !a); } else if (_$g.isArray(args[0])) { args = _$g.flatten(args[0]); } const auth = getAuthOptionsFromArgs(...args); if (isAuthOptions(auth)) { return authWithTenant(auth); } else if (args.length === 1 && _$g.isString(args[0])) { return undefined; } return getAuthOptionsFromEnv(); } function userAliasFromArgs(...args) { if (!args || !args.length) return undefined; if (args[0] == null) { args = _$g.dropWhile(args, (a) => !a); } else if (_$g.isArray(args[0])) { args = _$g.flatten(args[0]); } return args.length === 1 && _$g.isString(args[0]) ? args[0] : undefined; } function getAuthOptionsFromArgs(...args) { // do not call getAuthOptionsFromEnv() in here! // getAuthOptions("admin") // return envs admin_username | admin, admin_password if (!_$g.isEmpty(args) && _$g.isString(args[0])) { const user = Cypress.env(`${args[0]}_username`) || args[0]; const password = Cypress.env(`${args[0]}_password`); if (user && password) { return authWithTenant({ user, password, userAlias: args[0], }); } } // getAuthOptions({user: "abc", password: "abc"}, ...) if (!_$g.isEmpty(args) && _$g.isObjectLike(args[0])) { if (isAuthOptions(args[0])) { return authWithTenant(_$g.pick(args[0], ["user", "password", "tenant", "userAlias", "type"])); } // getAuthOptions({userAlias: "abc"}, ...) if (args[0].userAlias) { const user = Cypress.env(`${args[0].userAlias}_username`) || args[0].userAlias; const password = Cypress.env(`${args[0].userAlias}_password`); if (user && password) { return authWithTenant({ user, password, userAlias: args[0].userAlias, ...(args[0].type && { type: args[0].type }), }); } } // getAuthOptions({user: "abc", password: "abc"}, ...) if (args[0].username && args[0].password) { const auth = _$g.pick(args[0], [ "username", "password", "tenantId", "userAlias", ]); delete Object.assign(auth, { user: auth.username })["username"]; if (auth.tenantId) { delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"]; } return authWithTenant(auth); } // from IUser: getAuthOptions({userName: "abc", password: "abc"}, ...) if (args[0].userName && args[0].password) { const auth = _$g.pick(args[0], [ "userName", "password", "tenantId", "userAlias", ]); delete Object.assign(auth, { user: auth.userName })["userName"]; return authWithTenant(auth); } } // getAuthOptions("abc", "abc") if (args.length >= 2 && _$g.isString(args[0]) && _$g.isString(args[1])) { return authWithTenant({ 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 (_$g.isString(auth)) { authOptions = getAuthOptions(auth); } else if (_$g.isObjectLike(auth)) { if ("logout" in auth) { result = auth; } else { authOptions = auth; } } } if (!result) { const cookieAuth = new client.CookieAuth(); const token = util.get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN"); if (token?.trim() && !_$g.isEmpty(token.trim())) { result = cookieAuth; } else if (authOptions) { result = new client.BasicAuth(authOptions); } } return result; } function tenantFromBasicAuth(auth) { if (!auth || !_$g.isObjectLike(auth) || !auth.user) return undefined; const components = auth.user.split("/"); if (!components || components.length < 2) return undefined; return components[0]; } function authWithTenant(options) { const tenant = Cypress.env(`C8Y_TENANT`); if (tenant && !options.tenant) { _$g.extend(options, { tenant }); } return options; } 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 * * @returns Base URL from environment variables. */ function getBaseUrlFromEnv() { return (getEnvVar("C8Y_BASEURL") || getEnvVar("C8Y_BASE_URL") || Cypress.config().baseUrl || undefined); } 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"; 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, schemaGenerator: 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 client.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 { _: _$f } = 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 (_$f.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 === "C8Y_USERNAME" || key === "C8Y_PASSWORD") { 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}_username and ${userAlias}_password environment variables.`); } consoleProps.Yields = auth || null; logger.end(); return auth; } const { _: _$e } = Cypress; Cypress.Commands.add("hideCookieBanner", () => { Cypress.log({ name: "hideCookieBanner", }); cy.acceptCookieBanner(true, true, true); }); Cypress.Commands.add("acceptCookieBanner", (required = true, functional = true, marketing = true) => { const COOKIE_NAME = "acceptCookieNotice"; const consoleProps = { required, functional, marketing, }; Cypress.log({ name: "acceptCookieBanner", message: "", consoleProps: () => consoleProps, }); const setLocalCookie = (c) => { const cookie = JSON.stringify(c); window.localStorage.removeItem("__ccHideCookieBanner"); Cypress.on("window:before:load", (window) => { window.localStorage.setItem(COOKIE_NAME, cookie); }); window.localStorage.setItem(COOKIE_NAME, cookie); }; setLocalCookie({ required, functional, marketing }); cy.intercept({ pathname: /\/apps\/public\/public-options(@app-[^/]+)?\/options.json/, }, (request) => { request.on("before:response", (response) => { if (response.statusCode !== 200) { return; } if (window.localStorage.getItem("__ccHideCookieBanner") === "false") { return; } const policyVersion = response.body.cookieBanner?.policyVersion; const denyCookies = { required: !!required, functional: !!functional, marketing: !!marketing, }; if (policyVersion != null) { denyCookies.policyVersion = policyVersion; } setLocalCookie(denyCookies); }); }); }); Cypress.Commands.add("showCookieBanner", () => { Cypress.log({ name: "showCookieBanner", }); Cypress.on("window:before:load", (window) => { window.localStorage.removeItem("acceptCookieNotice"); }); window.localStorage.removeItem("acceptCookieNotice"); window.localStorage.setItem("__ccHideCookieBanner", "false"); }); Cypress.Commands.add("disableCookieBanner", () => { Cypress.log({ name: "disableCookieBanner", message: "", }); cy.intercept({ pathname: /\/apps\/public\/public-options(@app-[^/]+)?\/options.json/, }, (req) => { req.continue((res) => { res.body.cookieBanner = undefined; res.send(); }); }); }); Cypress.Commands.add("visitAndWaitForSelector", (url, language = "en", selector = "c8y-navigator-outlet c8y-app-icon", timeout = Cypress.config().pageLoadTimeout || 60000) => { const consoleProps = { url, language, selector, timeout, }; Cypress.log({ name: "visitAndWaitForSelector", message: url, consoleProps: () => consoleProps, }); cy.setLanguage(language); cy.visit(url); cy.get(selector, { timeout }).should("be.visible"); }); Cypress.Commands.add("setLanguage", (lang) => { 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 && _$e.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(); }); }); }); class TrieNode { constructor() { this.children = new Map(); this.isEndOfWord = false; this.count = 0; } } function insertWord(root, word) { let currentNode = root; for (let i = 0; i < word.length; i++) { const char = word[i]; if (!currentNode?.children.has(char)) { currentNode?.children.set(char, new TrieNode()); } currentNode = currentNode?.children.get(char); if (currentNode) { currentNode.count++; } } if (currentNode) { currentNode.isEndOfWord = true; } } function shortestUniquePrefixes(words) { const root = new TrieNode(); const prefixes = []; for (const word of words) { insertWord(root, word); } for (const word of words) { let currentNode = root; let prefix = ""; for (let i = 0; i < word.length; i++) { const char = word[i]; prefix += char; currentNode = currentNode?.children.get(char); if (currentNode && currentNode.count === 1) { prefixes.push(prefix); break; } } } return prefixes; } // default locales to be registered automatically const { _: _$d } = 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]; } async function registerLocale(data, c8yLocaleId, extraData = undefined, localeId) { const angularId = normalizeLocaleId(c8yLocaleId); LOCALE_DATA[angularId] = data; if (extraData) { LOCALE_DATA[angularId][NgLocaleDataIndex.ExtraData] = extraData; } const dfnsLocale = await loadDfnsLocale(getNgLocaleId(c8yLocaleId), localeId); LOCALE_DATA[angularId][NgLocaleDataIndex.DfnsLocale] = { ...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", }), }, }; } async function registerDefaultLocales() { await registerLocale( // @ts-expect-error !isModule(localeDe) ? localeDe : localeDe.default, "de"); await registerLocale( // @ts-expect-error !isModule(localeEn) ? localeEn : localeEn.default, "en"); } function normalizeLocaleId(localeId) { return localeId.toLowerCase().replace(/_/g, "-"); } 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 getLocaleTimeFormat(localeId, formatWidth); } function localizedDateFormat(localeId = "en", formatWidth = FormatWidth.Short) { return getLocaleDateFormat(localeId, formatWidth); } function localizedDateTimeFormat(localeId = "en", formatWidth = FormatWidth.Short) { const fullTime = getLocaleTimeFormat(localeId, formatWidth); const fullDate = getLocaleDateFormat(localeId, formatWidth); return formatDateTime(getLocaleDateTimeFormat(localeId, formatWidth), [ fullTime, fullDate, ]); } // https://github.com/angular/angular/blob/fe691935091aaf7090864c8111a15f7cc7e53b6c/packages/common/src/i18n/format_date.ts#L201 function formatDateTime(str, opt_values) { { str = str.replace(/\{([^}]+)}/g, function (match, key) { return opt_values != null && key in opt_values ? opt_values[key] : match; }); } return str; } function parseDate(date, format) { let parsedDate = undefined; // try to parse as number fist, if string is passed it might be converted without format being used if (_$d.isNumber(date)) { parsedDate = new Date(date); } // parse with format if (!isValidDate(parsedDate) && _$d.isString(date)) { parsedDate = Cypress.datefns.parse(date, format, new Date()); // if (!isValidDate(parsedDate) && _.isString(date)) { // parsedDate = new Date(date); // } } return parsedDate; } function isValidDate(date) { return date != null && !isNaN(date) && _$d.isDate(date); } function isModule(module) { return ( // @ts-expect-error module && _$d.isObject(module) && module.default && !_$d.isEmpty(module.default)); } function getLastDefinedValue(data, index) { for (let i = index; i > -1; i--) { if (typeof data[i] !== "undefined") { return data[i]; } } throw new Error("Locale data API: locale data undefined"); } function getLocaleTimeFormat(locale, width) { const data = getNgLocale(locale); return getLastDefinedValue(data[NgLocaleDataIndex.TimeFormat], width); } function getLocaleDateFormat(locale, width) { const data = getNgLocale(locale); return getLastDefinedValue(data[NgLocaleDataIndex.DateFormat], width); } function getLocaleDateTimeFormat(locale, width) { const data = getNgLocale(locale); const dateTimeFormatData = data[NgLocaleDataIndex.DateTimeFormat]; return getLastDefinedValue(dateTimeFormatData, width); } async function loadDfnsLocale(angularLocaleId, dfnsLocaleId) { const load = async (locale) => { try { const l = await import(`date-fns/locale/${locale}/`); return l.default; } catch (e) { console.error(e); return null; } }; if (!angularLocaleId && !dfnsLocaleId) return null; const r = load(dfnsLocaleId ?? angularLocaleId); if (r) { return r; } return null; } // var parseDayPatterns = { // narrow: [/^s/i, /^m/i, /^t/i, /^w/i, /^t/i, /^f/i, /^s/i], // any: [/^su/i, /^m/i, /^tu/i, /^w/i, /^th/i, /^f/i, /^sa/i] // }; function parseDayPatterns(locale) { const l = getNgLocale(locale); if (!l) return null; const dayData = l[NgLocaleDataIndex.DaysStandalone] ?? l[NgLocaleDataIndex.DaysFormat]; const result = { narrow: dayData[0].map((m) => new RegExp("^" + _$d.lowerCase(m).substring(0, 1), "i")), any: shortestUniquePrefixes(dayData[2]).map((m) => new RegExp("^" + _$d.lowerCase(m), "i")), }; return result; } // var matchDayPatterns = { // narrow: /^[smdmf]/i, // short: /^(so|mo|di|mi|do|fr|sa)/i, // abbreviated: /^(son?|mon?|die?|mit?|don?|fre?|sam?)\.?/i, // wide: /^(sonntag|montag|dienstag|mittwoch|donnerstag|freitag|samstag)/i // }; function matchDayPatterns(locale) { const l = getNgLocale(locale); if (!l) return null; const dayData = l[NgLocaleDataIndex.DaysStandalone] ?? l[NgLocaleDataIndex.DaysFormat]; const result = { narrow: new RegExp("^[" + _$d.uniq(dayData[0]).join("|") + "]", "i"), short: new RegExp("^(" + _$d.uniq(dayData[3]).join("|") + ")", "i"), abbreviated: new RegExp("^(" + dayData[1].join("|") + ")", "i"), wide: new RegExp("^(" + dayData[2].join("|") + ")", "i"), }; return result; } function parseMonthPatterns(locale) { const l = getNgLocale(locale); if (!l) return null; const monthData = l[NgLocaleDataIndex.MonthsStandalone] ?? l[NgLocaleDataIndex.MonthsFormat]; const result = { narrow: monthData[0].map((m) => new RegExp("^" + _$d.lowerCase(m).substring(0, 1), "i")), any: shortestUniquePrefixes(monthData[2]).map((m) => new RegExp("^" + _$d.lowerCase(m), "i")), }; return result; } function matchMonthPatterns(locale) { const l = getNgLocale(locale); if (!l) return null; const monthData = l[NgLocaleDataIndex.MonthsStandalone] ?? l[NgLocaleDataIndex.MonthsFormat]; const result = { narrow: new RegExp("^[" + _$d.uniq(monthData[0]).join("|") + "]", "i"), abbreviated: new RegExp("^(" + monthData[1].join("|") + ")", "i"), wide: new RegExp("^(" + monthData[2].join("|") + ")", "i"), }; return result; } function monthValuesForLocale(locale) { const l = getNgLocale(locale); if (!l) return null; const monthData = l[NgLocaleDataIndex.MonthsStandalone] ?? l[NgLocaleDataIndex.MonthsFormat]; const result = { narrow: monthData[0], abbreviated: monthData[1], wide: monthData[2], }; return result; } function dayValuesForLocale(locale) { const l = getNgLocale(locale); if (!l) return null; const monthData = l[NgLocaleDataIndex.DaysStandalone] ?? l[NgLocaleDataIndex.DaysFormat]; const result = { narrow: monthData[0], abbreviated: monthData[1], wide: monthData[2], }; return result; } const { _: _$c } = Cypress; Cypress.datefns = datefns__namespace; const defaultOptions = { log: true, invalid: "ignore", strictFormats: true, }; globalThis.registerLocale = registerLocale; globalThis.registerDefaultLocales = registerDefaultLocales; (async () => { await registerDefaultLocales(); })(); globalThis.setLocale = (localeId) => { const l = getNgLocale(localeId); if (l && _$c.isArray(l)) { Cypress.datefns.setDefaultOptions({ locale: l[NgLocaleDataIndex.DfnsLocale], }); } }; const isISODateSource = (arg) => { return arg != null && (_$c.isString(arg) || _$c.isNumber(arg) || _$c.isArray(arg)); }; const fromArguments = (args) => { let source = undefined; let options = undefined; if (args.length === 1) { source = args[0]; } else if (args.length > 1) { if (isISODateSource(args[1]) || typeof args[1] === "string") { source = args[1]; if (args.length > 2) { options = args[2]; } } else { source = args[0]; options = _$c.last(args); } } return [source, _$c.defaults({}, options, defaultOptions)]; }; Cypress.Commands.add("toDate", { prevSubject: "optional" }, (prevSubject, ...args) => { const [unsafeSource, options] = fromArguments([prevSubject, ...args]); const localizedFormats = options != null ? prepareLocalizedFormats(options) : []; const win = cy.state("window"); const language = options?.language ?? win.localStorage.getItem("c8y_language") ?? "en"; const consoleProps = options?.consoleProps ?? {}; if (!consoleProps.options) { consoleProps.options = options || null; } consoleProps.language = `${language} (${getNgLocaleId(language)})`; consoleProps.localizedFormats = localizedFormats || null; consoleProps.source = unsafeSource || null; let logger = options?.logger; let ourlogger = false; if (options?.log === true && options?.consoleProps == null) { logger = Cypress.log({ name: "toDate", message: `${unsafeSource || null}`, consoleProps: () => consoleProps, autoEnd: false, }); ourlogger = true; } if (!unsafeSource) { logger?.end(); throwError(`No or undefined source provided to cy.toDate.`); } const source = unsafeSource; const input = Array.isArray(source) ? source : [source]; const formats = []; let dates = input.map((item) => { let parsedDate; // try to read date from Angular date formats or number for (const format of loc