@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.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.d.ts +2 -0
- package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.dev.js +628 -0
- package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.js +7 -0
- package/add-commands/dist/commercetools-frontend-cypress-add-commands.cjs.prod.js +628 -0
- package/add-commands/dist/commercetools-frontend-cypress-add-commands.esm.js +610 -0
- package/add-commands/index.d.ts +68 -0
- package/add-commands/package.json +5 -0
- package/dist/commercetools-frontend-cypress.cjs.d.ts +2 -0
- package/dist/commercetools-frontend-cypress.cjs.dev.js +13 -0
- package/dist/commercetools-frontend-cypress.cjs.js +7 -0
- package/dist/commercetools-frontend-cypress.cjs.prod.js +13 -0
- package/dist/commercetools-frontend-cypress.esm.js +6 -0
- package/dist/constants-009cb682.cjs.dev.js +17 -0
- package/dist/constants-13c76918.esm.js +13 -0
- package/dist/constants-2f1475a6.cjs.prod.js +17 -0
- package/dist/declarations/src/add-commands/index.d.ts +5 -0
- package/dist/declarations/src/add-commands/login.d.ts +71 -0
- package/dist/declarations/src/constants.d.ts +11 -0
- package/dist/declarations/src/index.d.ts +2 -0
- package/dist/declarations/src/task/index.d.ts +13 -0
- package/dist/declarations/src/version.d.ts +2 -0
- package/package.json +59 -0
- package/task/dist/commercetools-frontend-cypress-task.cjs.d.ts +3 -0
- package/task/dist/commercetools-frontend-cypress-task.cjs.dev.js +129 -0
- package/task/dist/commercetools-frontend-cypress-task.cjs.js +7 -0
- package/task/dist/commercetools-frontend-cypress-task.cjs.prod.js +129 -0
- package/task/dist/commercetools-frontend-cypress-task.esm.js +108 -0
- 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
|
+
});
|