@commercetools-frontend/cypress 0.0.0-CRAFT-1791-20251006162610

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.d.ts +2 -0
  4. package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.dev.js +628 -0
  5. package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.js +7 -0
  6. package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.prod.js +628 -0
  7. package/add-commands/dist/commercetools-frontend-cypress-add-commands.esm.js +610 -0
  8. package/add-commands/index.d.ts +68 -0
  9. package/add-commands/package.json +5 -0
  10. package/dist/commercetools-frontend-cypress.cjs.d.ts +2 -0
  11. package/dist/commercetools-frontend-cypress.cjs.dev.js +13 -0
  12. package/dist/commercetools-frontend-cypress.cjs.js +7 -0
  13. package/dist/commercetools-frontend-cypress.cjs.prod.js +13 -0
  14. package/dist/commercetools-frontend-cypress.esm.js +6 -0
  15. package/dist/constants-009cb682.cjs.dev.js +17 -0
  16. package/dist/constants-13c76918.esm.js +13 -0
  17. package/dist/constants-2f1475a6.cjs.prod.js +17 -0
  18. package/dist/declarations/src/add-commands/index.d.ts +5 -0
  19. package/dist/declarations/src/add-commands/login.d.ts +71 -0
  20. package/dist/declarations/src/constants.d.ts +11 -0
  21. package/dist/declarations/src/index.d.ts +2 -0
  22. package/dist/declarations/src/task/index.d.ts +13 -0
  23. package/dist/declarations/src/version.d.ts +2 -0
  24. package/package.json +59 -0
  25. package/task/dist/commercetools-frontend-cypress-task.cjs.d.ts +3 -0
  26. package/task/dist/commercetools-frontend-cypress-task.cjs.dev.js +129 -0
  27. package/task/dist/commercetools-frontend-cypress-task.cjs.js +7 -0
  28. package/task/dist/commercetools-frontend-cypress-task.cjs.prod.js +129 -0
  29. package/task/dist/commercetools-frontend-cypress-task.esm.js +108 -0
  30. package/task/package.json +4 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 commercetools GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @commercetools-frontend/cypress
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/@commercetools-frontend/cypress"><img src="https://badgen.net/npm/v/@commercetools-frontend/cypress" alt="Latest release (latest dist-tag)" /></a> <a href="https://www.npmjs.com/package/@commercetools-frontend/cypress"><img src="https://badgen.net/npm/v/@commercetools-frontend/cypress/next" alt="Latest release (next dist-tag)" /></a> <a href="https://bundlephobia.com/result?p=@commercetools-frontend/cypress"><img src="https://badgen.net/bundlephobia/minzip/@commercetools-frontend/cypress" alt="Minified + GZipped size" /></a> <a href="https://github.com/commercetools/merchant-center-application-kit/blob/main/LICENSE"><img src="https://badgen.net/github/license/commercetools/merchant-center-application-kit" alt="GitHub license" /></a>
5
+ </p>
6
+
7
+ Check out the [documentation](https://docs.commercetools.com/merchant-center-customizations/api-reference/commercetools-frontend-cypress) for more information.
@@ -0,0 +1,2 @@
1
+ export * from "../../dist/declarations/src/add-commands/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWVyY2V0b29scy1mcm9udGVuZC1jeXByZXNzLWFkZC1jb21tYW5kcy5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL2Rpc3QvZGVjbGFyYXRpb25zL3NyYy9hZGQtY29tbWFuZHMvaW5kZXguZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
@@ -0,0 +1,628 @@
1
+ 'use strict';
2
+
3
+ var _Object$keys = require('@babel/runtime-corejs3/core-js-stable/object/keys');
4
+ var _Object$getOwnPropertySymbols = require('@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols');
5
+ var _filterInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/filter');
6
+ var _Object$getOwnPropertyDescriptor = require('@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor');
7
+ var _forEachInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/for-each');
8
+ var _Object$getOwnPropertyDescriptors = require('@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors');
9
+ var _Object$defineProperties = require('@babel/runtime-corejs3/core-js-stable/object/define-properties');
10
+ var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property');
11
+ var _defineProperty = require('@babel/runtime-corejs3/helpers/defineProperty');
12
+ var _findInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/find');
13
+ var constants = require('@commercetools-frontend/constants');
14
+ var _URL = require('@babel/runtime-corejs3/core-js-stable/url');
15
+ var semver = require('semver');
16
+ var constants$1 = require('../../dist/constants-009cb682.cjs.dev.js');
17
+ var _slicedToArray = require('@babel/runtime-corejs3/helpers/slicedToArray');
18
+ var _Array$from = require('@babel/runtime-corejs3/core-js-stable/array/from');
19
+ var _reduceRightInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/reduce-right');
20
+
21
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
22
+
23
+ var _Object$keys__default = /*#__PURE__*/_interopDefault(_Object$keys);
24
+ var _Object$getOwnPropertySymbols__default = /*#__PURE__*/_interopDefault(_Object$getOwnPropertySymbols);
25
+ var _filterInstanceProperty__default = /*#__PURE__*/_interopDefault(_filterInstanceProperty);
26
+ var _Object$getOwnPropertyDescriptor__default = /*#__PURE__*/_interopDefault(_Object$getOwnPropertyDescriptor);
27
+ var _forEachInstanceProperty__default = /*#__PURE__*/_interopDefault(_forEachInstanceProperty);
28
+ var _Object$getOwnPropertyDescriptors__default = /*#__PURE__*/_interopDefault(_Object$getOwnPropertyDescriptors);
29
+ var _Object$defineProperties__default = /*#__PURE__*/_interopDefault(_Object$defineProperties);
30
+ var _Object$defineProperty__default = /*#__PURE__*/_interopDefault(_Object$defineProperty);
31
+ var _findInstanceProperty__default = /*#__PURE__*/_interopDefault(_findInstanceProperty);
32
+ var _URL__default = /*#__PURE__*/_interopDefault(_URL);
33
+ var semver__default = /*#__PURE__*/_interopDefault(semver);
34
+ var _Array$from__default = /*#__PURE__*/_interopDefault(_Array$from);
35
+ var _reduceRightInstanceProperty__default = /*#__PURE__*/_interopDefault(_reduceRightInstanceProperty);
36
+
37
+ function ownKeys$1(e, r) { var t = _Object$keys__default["default"](e); if (_Object$getOwnPropertySymbols__default["default"]) { var o = _Object$getOwnPropertySymbols__default["default"](e); r && (o = _filterInstanceProperty__default["default"](o).call(o, function (r) { return _Object$getOwnPropertyDescriptor__default["default"](e, r).enumerable; })), t.push.apply(t, o); } return t; }
38
+ function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty__default["default"](_context2 = ownKeys$1(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors__default["default"] ? _Object$defineProperties__default["default"](e, _Object$getOwnPropertyDescriptors__default["default"](t)) : _forEachInstanceProperty__default["default"](_context3 = ownKeys$1(Object(t))).call(_context3, function (r) { _Object$defineProperty__default["default"](e, r, _Object$getOwnPropertyDescriptor__default["default"](t, r)); }); } return e; }
39
+
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+
44
+ // Alias for backwards compatibility
45
+
46
+ const defaultTimeouts = {
47
+ waitForRedirectToLogin: 1000
48
+ };
49
+ function isFeatureSupported(expectedVersion) {
50
+ return semver__default["default"].gte(Cypress.version, expectedVersion);
51
+ }
52
+ function isRunningOnLocalhost() {
53
+ const baseUrl = new _URL__default["default"](Cypress.config('baseUrl'));
54
+ return baseUrl.hostname === 'localhost';
55
+ }
56
+ const isCustomView = commandOptions => commandOptions.entryPointUriPath === constants.CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH;
57
+ function loginByForm(commandOptions) {
58
+ const isLocalhost = isRunningOnLocalhost();
59
+ Cypress.log({
60
+ name: 'isLocalhost',
61
+ message: isLocalhost
62
+ });
63
+ const projectKey = commandOptions.projectKey ?? Cypress.env('PROJECT_KEY');
64
+ const isCustomViewConfigCommand = isCustomView(commandOptions);
65
+ const customEntityConfigCommand = isCustomViewConfigCommand ? 'customViewConfig' : 'customApplicationConfig';
66
+ const packageName = commandOptions.packageName ?? Cypress.env('PACKAGE_NAME');
67
+ cy.task(customEntityConfigCommand, _objectSpread$1({
68
+ entryPointUriPath: commandOptions.entryPointUriPath,
69
+ dotfiles: commandOptions.dotfiles
70
+ }, isCustomViewConfigCommand ? {
71
+ packageName
72
+ } : {}),
73
+ // Do not show log, as it may contain sensible information.
74
+ {
75
+ log: false
76
+ }).then(appConfig => {
77
+ let initialUriPath = `/${projectKey}/${commandOptions.entryPointUriPath}`;
78
+ if (commandOptions.entryPointUriPath === 'account') {
79
+ initialUriPath = `/${commandOptions.entryPointUriPath}`;
80
+ }
81
+
82
+ // Log loaded application config for debugging purposes.
83
+ Cypress.log({
84
+ displayName: 'task',
85
+ name: customEntityConfigCommand,
86
+ message: appConfig
87
+ });
88
+ const userCredentials = commandOptions.login ?? {
89
+ email: Cypress.env('LOGIN_EMAIL') || Cypress.env('LOGIN_USER'),
90
+ password: Cypress.env('LOGIN_PASSWORD')
91
+ };
92
+ const sessionKey = ['loginByForm', userCredentials.email, commandOptions.entryPointUriPath];
93
+
94
+ /**
95
+ * The function is used by Cypress `session` command to store the
96
+ * browser state after executing the authentication flow.
97
+ */
98
+ function sessionAuthCallback() {
99
+ // Get the feature flags from the Merchant Center API so we can
100
+ // check whether Identity is enabled
101
+ cy.request({
102
+ method: 'POST',
103
+ url: `${appConfig.mcApiUrl}/graphql`,
104
+ body: {
105
+ operationName: 'AllFeatures',
106
+ query: `query AllFeatures { allFeatures { name value } }`
107
+ },
108
+ headers: {
109
+ 'content-type': 'application/json',
110
+ 'x-graphql-target': constants.GRAPHQL_TARGETS.MERCHANT_CENTER_BACKEND,
111
+ 'x-graphql-operation-name': 'AllFeatures',
112
+ 'x-project-key': projectKey
113
+ }
114
+ }).then(res => {
115
+ var _context;
116
+ const enableIdentity = _findInstanceProperty__default["default"](_context = res.body.data.allFeatures).call(_context, feature => feature.name === 'enableGlobalIdentity');
117
+ const isGlobalIdentityEnabled = Boolean(enableIdentity?.value);
118
+ Cypress.log({
119
+ name: 'isGlobalIdentityEnabled',
120
+ message: isGlobalIdentityEnabled
121
+ });
122
+ const identityUrl = Cypress.env('IDENTITY_URL') || 'https://identity.commercetools.com';
123
+
124
+ /**
125
+ * There are different scenarios and variations on the flow depending
126
+ * on the environment (localhost, production) and if Identity is enabled.
127
+ *
128
+ * # When the application runs on localhost:3001.
129
+ *
130
+ * ## When Identity is enabled
131
+ * - The test visits the application at localhost:3001.
132
+ * - Cypress registers the default origin being localhost:3001.
133
+ * - The application is redirected to the Identity URL.
134
+ * - Cypress interacts with the Identity URL via `cy.origin`.
135
+ * - At the end of the flow, the test interacts with the application at localhost:3001.
136
+ * ## When Identity is disabled
137
+ * - The test visits the application at localhost:3001.
138
+ * - Cypress registers the default origin being localhost:3001.
139
+ * - The application is redirected to the Merchant Center login page.
140
+ * - Cypress interacts with the Merchant Center login page via `cy.origin`.
141
+ * - At the end of the flow, the test interacts with the application at localhost:3001.
142
+ *
143
+ * # When the application runs on a production URL.
144
+ *
145
+ * ## When Identity is enabled
146
+ * - The test visits the application at the production URL.
147
+ * - The MC Proxy performs a server-side redirect to the Identity URL.
148
+ * - Cypress registers the default origin being the Identity URL.
149
+ * - Cypress interacts with the Identity URL.
150
+ * - At the end of the flow, the test interacts with the application at the production URL via `cy.origin`.
151
+ *
152
+ * ## When Identity is disabled
153
+ * - The test visits the application at the production URL.
154
+ * - The MC Proxy renders the Merchant Center login page.
155
+ * - Cypress registers the default origin being the Merchant Center URL.
156
+ * - Cypress interacts with the Merchant Center login page.
157
+ * - At the end of the flow, the test interacts with the application at the production URL.
158
+ */
159
+
160
+ if (isLocalhost) {
161
+ // Visit the special endpoint /api/health to ensure that the primary origin is set to localhost.
162
+ cy.visit('/api/health');
163
+ // Visit the application URL, which triggers then the login flow.
164
+ cy.visit(initialUriPath, {
165
+ onBeforeLoad: commandOptions.onBeforeLoad
166
+ });
167
+ // Cypress gets confused when switching between origins, likely because
168
+ // the redirect to Identity/MC doesn't happen immediately.
169
+ // If we don't wait, Cypress fails as it tries to interact with `cy.origin`
170
+ // but the test is still in the initial origin URL.
171
+ // This is a bit unexpected and to be considered a workaround.
172
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
173
+ cy.wait(commandOptions.timeouts?.waitForRedirectToLogin ?? defaultTimeouts.waitForRedirectToLogin);
174
+ if (isGlobalIdentityEnabled) {
175
+ // Use cy.origin to handle the identity domain
176
+ cy.origin(identityUrl, {
177
+ args: {
178
+ userCredentials,
179
+ identityUrl
180
+ }
181
+ }, _ref => {
182
+ let userCredentials = _ref.userCredentials,
183
+ identityUrl = _ref.identityUrl;
184
+ cy.url().should('include', `${identityUrl}/login`);
185
+ // Fill in the email and click Next
186
+ cy.get('input[name="identifier"]').type(userCredentials.email);
187
+ cy.get('button').contains('Next').click();
188
+
189
+ // Wait for the password form to appear
190
+ cy.get('input[name="password"]').should('be.visible');
191
+ // Fill in the password and submit
192
+ cy.get('input[name="password"]').type(userCredentials.password, {
193
+ log: false,
194
+ force: true
195
+ });
196
+ cy.get('button').contains('Submit').click({
197
+ force: true
198
+ });
199
+ });
200
+ } else {
201
+ const mcUrl = appConfig.mcApiUrl.replace('mc-api', 'mc');
202
+ cy.origin(mcUrl, {
203
+ args: {
204
+ userCredentials,
205
+ mcUrl
206
+ }
207
+ }, _ref2 => {
208
+ let userCredentials = _ref2.userCredentials,
209
+ mcUrl = _ref2.mcUrl;
210
+ cy.url().should('include', `${mcUrl}/login`);
211
+
212
+ // Same as `fillLegacyLoginFormWithRetry`.
213
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
214
+ cy.get('input[name=email]')
215
+ // We use `force` as the MC login UI (production) in tests renders the
216
+ // cookie banner overlapping the input fields.
217
+ // To allow Cypress to interact with the input fields, we use `force`.
218
+ .clear({
219
+ force: true
220
+ }).type(userCredentials.email, {
221
+ force: true
222
+ });
223
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
224
+ cy.get('input[name=password]')
225
+ // We use `force` as the MC login UI (production) in tests renders the
226
+ // cookie banner overlapping the input fields.
227
+ // To allow Cypress to interact with the input fields, we use `force`.
228
+ .clear({
229
+ force: true
230
+ }).type(userCredentials.password, {
231
+ log: false,
232
+ force: true
233
+ });
234
+ cy.get('button').contains('Sign in').click({
235
+ force: true
236
+ });
237
+ });
238
+ }
239
+ // Wait for the flow to redirect back to the application.
240
+ cy.get('[role="main"]').should('exist');
241
+ cy.url().should('include', initialUriPath);
242
+ } else {
243
+ // Ensure the primary origin is set to MC.
244
+ cy.visit(new _URL__default["default"]('/health', Cypress.config('baseUrl')).toString());
245
+ if (isGlobalIdentityEnabled) {
246
+ const identityArgs = {
247
+ email: userCredentials.email,
248
+ password: userCredentials.password,
249
+ returnTo: new _URL__default["default"](initialUriPath, Cypress.config('baseUrl')).toString()
250
+ };
251
+
252
+ // Perform the login flow through Identity.
253
+ cy.origin('https://identity.commercetools.com', {
254
+ args: identityArgs
255
+ }, args => {
256
+ cy.visit({
257
+ url: `/login`,
258
+ qs: {
259
+ returnTo: args.returnTo
260
+ }
261
+ });
262
+ // Fill in the email and click Next
263
+ cy.get('input[name="identifier"]').type(args.email);
264
+ cy.get('button').contains('Next').click();
265
+
266
+ // Wait for the password form to appear
267
+ cy.get('input[name="password"]').should('be.visible');
268
+ // Fill in the password and submit
269
+ cy.get('input[name="password"]').type(args.password, {
270
+ log: false,
271
+ force: true
272
+ });
273
+ cy.get('button').contains('Submit').click({
274
+ force: true
275
+ });
276
+ });
277
+ } else {
278
+ // Legacy login flow.
279
+ // Visit the application URL, which triggers then the login flow.
280
+ cy.visit(initialUriPath, {
281
+ onBeforeLoad: commandOptions.onBeforeLoad
282
+ });
283
+ fillLegacyLoginFormWithRetry(userCredentials);
284
+ }
285
+ // Wait for the flow to redirect back to the application.
286
+ cy.get('[role="main"]').should('exist');
287
+ cy.url().should('include', initialUriPath);
288
+ }
289
+ });
290
+ }
291
+
292
+ // For backwards compatibility.
293
+ if (isFeatureSupported('12.0.0') || Cypress.config('experimentalSessionAndOrigin')) {
294
+ // https://www.cypress.io/blog/2021/08/04/authenticate-faster-in-tests-cy-session-command/
295
+ cy.session(sessionKey, sessionAuthCallback, isFeatureSupported('10.9.0') ? {
296
+ cacheAcrossSpecs: typeof commandOptions.disableCacheAcrossSpecs === 'boolean' ? !commandOptions.disableCacheAcrossSpecs : true
297
+ // TODO: enable it once Identity is enabled by default.
298
+ // validate() {
299
+ // if (isLocalhost) {
300
+ // return;
301
+ // }
302
+ // // Verify that the session is valid (session cookie is included in the request).
303
+ // cy.request(new URL('/whoami', appConfig.mcApiUrl).toString())
304
+ // .its('status')
305
+ // .should('eq', 200);
306
+ // },
307
+ } : undefined);
308
+ } else {
309
+ cy.log(`We recommend to use "cy.session" to reduce the time to log in between tests. Make sure to have at least Cypress v12 or enable it via "experimentalSessionAndOrigin" for older Cypress versions.`);
310
+ sessionAuthCallback();
311
+ }
312
+ if (commandOptions.initialRoute) {
313
+ cy.visit(`${Cypress.config('baseUrl')}${commandOptions.initialRoute}`);
314
+ cy.url().should('include', commandOptions.initialRoute);
315
+ }
316
+ });
317
+ }
318
+
319
+ /* Utilities */
320
+
321
+ const legacyMaxLoginAttempts = Cypress.config('maxLoginAttempts') ?? 3;
322
+ function fillLegacyLoginFormWithRetry(userCredentials) {
323
+ // Intercept the login request so we can retry it if we receive a TOO_MANY_REQUESTS status code
324
+ cy.intercept('POST', '**/tokens').as('loginRequest');
325
+ function getRandomDelayInSeconds() {
326
+ const minSeconds = 0.5;
327
+ const maxSeconds = 1.5;
328
+ return (Math.random() * (maxSeconds - minSeconds) + minSeconds) * 1000;
329
+ }
330
+ function attemptLogin(attemptsLeft) {
331
+ if (attemptsLeft <= 0) {
332
+ throw new Error(`All login attempts exhausted. Please check your credentials.`);
333
+ }
334
+ cy.log(`Attempts left: ${attemptsLeft}`);
335
+
336
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
337
+ cy.get('input[name=email]').clear({
338
+ force: true
339
+ }).type(userCredentials.email, {
340
+ force: true
341
+ });
342
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
343
+ cy.get('input[name=password]').clear({
344
+ force: true
345
+ }).type(userCredentials.password, {
346
+ log: false,
347
+ force: true
348
+ });
349
+ cy.get('button').contains('Sign in').click({
350
+ force: true
351
+ });
352
+
353
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
354
+ cy.wait('@loginRequest').then(interception => {
355
+ const statusCode = interception.response.statusCode;
356
+ cy.log('Login request status code:', statusCode);
357
+ if (statusCode === constants$1.HTTP_STATUS_CODES.TOO_MANY_REQUESTS) {
358
+ // We wait for something between 0.5 and 1.5 seconds before retrying
359
+ cy.wait(getRandomDelayInSeconds());
360
+ attemptLogin(attemptsLeft - 1);
361
+ } else {
362
+ cy.log('Login successful');
363
+ }
364
+ });
365
+ }
366
+ attemptLogin(legacyMaxLoginAttempts);
367
+ }
368
+
369
+ /**
370
+ * NOTE: the `realHover` command is originally being implemented in `cypress-real-events` package.
371
+ * https://github.com/dmtrKovalenko/cypress-real-events/blob/develop/src/commands/realHover.ts
372
+ *
373
+ * However, due to known issues with conflicting types between Cypress and Jest, importing the `cypress-real-events`
374
+ * package here will cause such issues with TypeScript as our `@commercetools-frontend/cypress` package
375
+ * is checked and built together with all other packages and not in isolation.
376
+ * See https://docs.cypress.io/guides/tooling/typescript-support#Clashing-types-with-Jest.
377
+ *
378
+ * Therefore, we are porting here the implementation of `realHover` to avoid importing it from the
379
+ * original package `cypress-real-events`.
380
+ */
381
+
382
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
383
+
384
+ async function fireCdpCommand(command, params) {
385
+ return Cypress.automation('remote:debugger:protocol', {
386
+ command,
387
+ params
388
+ }).catch(error => {
389
+ throw new Error(`Failed request to chrome devtools protocol. This can happen if cypress lost connection to the browser or the command itself is not valid. Original cypress error: ${error}`);
390
+ });
391
+ }
392
+ function getPositionedCoordinates(x0, y0, width, height, position, frameScale) {
393
+ if (typeof position === 'object' && position !== null) {
394
+ const x = position.x,
395
+ y = position.y;
396
+ // scale the position coordinates according to the viewport scale
397
+ return [x0 + x * frameScale, y0 + y * frameScale];
398
+ }
399
+ switch (position) {
400
+ case 'topLeft':
401
+ return [x0, y0];
402
+ case 'top':
403
+ return [x0 + width / 2, y0];
404
+ case 'topRight':
405
+ return [x0 + width - 1, y0];
406
+ case 'left':
407
+ return [x0, y0 + height / 2];
408
+ case 'right':
409
+ return [x0 + width - 1, y0 + height / 2];
410
+ case 'bottomLeft':
411
+ return [x0, y0 + height - 1];
412
+ case 'bottom':
413
+ return [x0 + width / 2, y0 + height - 1];
414
+ case 'bottomRight':
415
+ return [x0 + width - 1, y0 + height - 1];
416
+ // center by default
417
+ default:
418
+ return [x0 + width / 2, y0 + height / 2];
419
+ }
420
+ }
421
+ /**
422
+ * Scrolls the given htmlElement into view on the page.
423
+ * The position the element is scrolled to can be customized with scrollBehavior.
424
+ */
425
+ function scrollIntoView(htmlElement) {
426
+ let scrollBehavior = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';
427
+ let block;
428
+ if (scrollBehavior === 'top') {
429
+ block = 'start';
430
+ } else if (scrollBehavior === 'bottom') {
431
+ block = 'end';
432
+ } else {
433
+ block = scrollBehavior;
434
+ }
435
+ htmlElement.scrollIntoView({
436
+ block
437
+ });
438
+ }
439
+
440
+ // for cross origin domains .frameElement returns null so query using parentWindow
441
+ // but when running using --disable-web-security it will return the frame element
442
+ function getFrameElement(currentWindow) {
443
+ var _context;
444
+ if (currentWindow.frameElement) {
445
+ // accessible for same-origin iframes
446
+ // or when running with --disable-web-security
447
+ return currentWindow.frameElement;
448
+ }
449
+
450
+ // fallback to querying using the parent window, mainly to grab the AUT iframe
451
+ const iframeElements = currentWindow.parent.document.querySelectorAll('iframe');
452
+ return _findInstanceProperty__default["default"](_context = _Array$from__default["default"](iframeElements)).call(_context, element => element.contentWindow === currentWindow);
453
+ }
454
+ function getIframesPositionShift(element) {
455
+ let currentWindow = element.ownerDocument.defaultView;
456
+ const noPositionShift = {
457
+ frameScale: 1,
458
+ frameX: 0,
459
+ frameY: 0
460
+ };
461
+ if (!currentWindow) {
462
+ return noPositionShift;
463
+ }
464
+
465
+ // eslint-disable-next-line prefer-const
466
+ const iframes = [];
467
+ while (currentWindow !== window.top) {
468
+ const element = getFrameElement(currentWindow);
469
+ if (element) {
470
+ iframes.push(element);
471
+ }
472
+ currentWindow = currentWindow.parent;
473
+ }
474
+ return _reduceRightInstanceProperty__default["default"](iframes).call(iframes, (_ref, frame) => {
475
+ let frameX = _ref.frameX,
476
+ frameY = _ref.frameY,
477
+ frameScale = _ref.frameScale;
478
+ const _frame$getBoundingCli = frame.getBoundingClientRect(),
479
+ x = _frame$getBoundingCli.x,
480
+ y = _frame$getBoundingCli.y,
481
+ width = _frame$getBoundingCli.width;
482
+ return {
483
+ frameX: frameX + x * frameScale,
484
+ frameY: frameY + y * frameScale,
485
+ frameScale: frameScale * (width / frame.offsetWidth)
486
+ };
487
+ }, noPositionShift);
488
+ }
489
+
490
+ /**
491
+ * Returns the coordinates and size of a given Element, relative to the Cypress app <iframe>.
492
+ * Accounts for any scaling on the iframes.
493
+ */
494
+ function getElementPositionXY(htmlElement) {
495
+ const _htmlElement$getBound = htmlElement.getBoundingClientRect(),
496
+ elementX = _htmlElement$getBound.x,
497
+ elementY = _htmlElement$getBound.y,
498
+ width = _htmlElement$getBound.width,
499
+ height = _htmlElement$getBound.height;
500
+ const _getIframesPositionSh = getIframesPositionShift(htmlElement),
501
+ frameScale = _getIframesPositionSh.frameScale,
502
+ frameX = _getIframesPositionSh.frameX,
503
+ frameY = _getIframesPositionSh.frameY;
504
+ return {
505
+ x: frameX + elementX * frameScale,
506
+ y: frameY + elementY * frameScale,
507
+ width: width * frameScale,
508
+ height: height * frameScale,
509
+ frameScale: frameScale
510
+ };
511
+ }
512
+ function getCypressElementCoordinates(
513
+ // @ts-ignore
514
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
515
+ jqueryEl, position, scrollBehavior) {
516
+ const htmlElement = jqueryEl.get(0);
517
+ const cypressAppFrame = window.parent.document.querySelector('iframe');
518
+ if (!cypressAppFrame) {
519
+ throw new Error('Can not find cypress application iframe, it looks like critical issue. Please rise an issue on GitHub.');
520
+ }
521
+ const effectiveScrollBehavior = scrollBehavior ?? Cypress.config('scrollBehavior') ?? 'center';
522
+ if (effectiveScrollBehavior && typeof effectiveScrollBehavior !== 'object') {
523
+ scrollIntoView(htmlElement, effectiveScrollBehavior);
524
+ }
525
+ const _getElementPositionXY = getElementPositionXY(htmlElement),
526
+ x = _getElementPositionXY.x,
527
+ y = _getElementPositionXY.y,
528
+ width = _getElementPositionXY.width,
529
+ height = _getElementPositionXY.height,
530
+ frameScale = _getElementPositionXY.frameScale;
531
+ const _getPositionedCoordin = getPositionedCoordinates(x, y, width, height, position ?? 'center', frameScale),
532
+ _getPositionedCoordin2 = _slicedToArray(_getPositionedCoordin, 2),
533
+ posX = _getPositionedCoordin2[0],
534
+ posY = _getPositionedCoordin2[1];
535
+ return {
536
+ x: posX,
537
+ y: posY,
538
+ frameScale: frameScale
539
+ };
540
+ }
541
+ const keyToModifierBitMap = {
542
+ Alt: 1,
543
+ Control: 2,
544
+ Meta: 4,
545
+ Shift: 8
546
+ };
547
+ async function realHover(
548
+ // @ts-ignore
549
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
550
+ subject) {
551
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
552
+ const _getCypressElementCoo = getCypressElementCoordinates(subject, options.position, options.scrollBehavior),
553
+ x = _getCypressElementCoo.x,
554
+ y = _getCypressElementCoo.y;
555
+ const log = Cypress.log({
556
+ $el: subject,
557
+ name: 'realHover',
558
+ consoleProps: () => ({
559
+ 'Applied To': subject.get(0),
560
+ 'Absolute Coordinates': {
561
+ x,
562
+ y
563
+ }
564
+ })
565
+ });
566
+ await fireCdpCommand('Input.dispatchMouseEvent', {
567
+ x,
568
+ y,
569
+ type: 'mouseMoved',
570
+ button: 'none',
571
+ pointerType: options.pointer ?? 'mouse',
572
+ modifiers: options.shiftKey ? keyToModifierBitMap.Shift : 0
573
+ });
574
+ log.snapshot().end();
575
+ return subject;
576
+ }
577
+
578
+ function ownKeys(e, r) { var t = _Object$keys__default["default"](e); if (_Object$getOwnPropertySymbols__default["default"]) { var o = _Object$getOwnPropertySymbols__default["default"](e); r && (o = _filterInstanceProperty__default["default"](o).call(o, function (r) { return _Object$getOwnPropertyDescriptor__default["default"](e, r).enumerable; })), t.push.apply(t, o); } return t; }
579
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty__default["default"](_context2 = ownKeys(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors__default["default"] ? _Object$defineProperties__default["default"](e, _Object$getOwnPropertyDescriptors__default["default"](t)) : _forEachInstanceProperty__default["default"](_context3 = ownKeys(Object(t))).call(_context3, function (r) { _Object$defineProperty__default["default"](e, r, _Object$getOwnPropertyDescriptor__default["default"](t, r)); }); } return e; }
580
+
581
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
582
+
583
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
584
+
585
+ Cypress.Commands.add('loginToMerchantCenter', commandOptions => {
586
+ Cypress.log({
587
+ name: 'loginToMerchantCenter'
588
+ });
589
+ loginByForm(commandOptions);
590
+ });
591
+ Cypress.Commands.add('loginToMerchantCenterForCustomView', commandOptions => {
592
+ Cypress.log({
593
+ name: 'loginToMerchantCenterForCustomView'
594
+ });
595
+ const projectKey = Cypress.env('PROJECT_KEY');
596
+ loginByForm(_objectSpread(_objectSpread({}, commandOptions), {}, {
597
+ entryPointUriPath: constants.CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH,
598
+ initialRoute: `/${projectKey}/${constants.CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH}`
599
+ }));
600
+ });
601
+ Cypress.Commands.add('loginByOidc', commandOptions => {
602
+ Cypress.log({
603
+ name: 'loginByOidc'
604
+ });
605
+ cy.log('We recommend not to use the command "cy.loginByOidc" directly. Instead, use the more generic "cy.loginToMerchantCenter" command as it automatically detects which login mechanism to use.');
606
+ loginByForm(commandOptions);
607
+ });
608
+ Cypress.Commands.add('hover', {
609
+ prevSubject: true
610
+ }, realHover);
611
+ Cypress.Commands.add('showNavigationSubmenuItems', menuItemTextMatcher => {
612
+ cy.findByTestId('left-navigation').findByText(menuItemTextMatcher).parents('[role="menuitem"]').first()
613
+ // Refers to the custom command "hover"
614
+ .hover();
615
+ });
616
+
617
+ // https://github.com/cypress-io/cypress/issues/136#issuecomment-342391119
618
+ Cypress.Commands.add('getIframeBody', {
619
+ prevSubject: 'element'
620
+ },
621
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
622
+ $iframe => {
623
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
624
+ return new Cypress.Promise(resolve => {
625
+ var _context;
626
+ resolve(_findInstanceProperty__default["default"](_context = $iframe.contents()).call(_context, 'body'));
627
+ });
628
+ });
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./commercetools-frontend-cypress-add-commands.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./commercetools-frontend-cypress-add-commands.cjs.dev.js");
7
+ }