@zohodesk/testinglibrary 0.0.65-n20-experimental → 0.0.66-n20-experimental
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/build/common/data-generator/steps/DataGenerator.spec.js +8 -4
- package/build/common/data-generator/steps/DataGeneratorStepsHelper.js +2 -2
- package/build/core/dataGenerator/DataGenerator.js +9 -4
- package/build/core/dataGenerator/authenticator/Authenticator.js +35 -0
- package/build/core/dataGenerator/authenticator/CookieAuthenticator.js +41 -0
- package/build/core/dataGenerator/authenticator/JWTauthenticator.js +71 -0
- package/build/core/dataGenerator/authenticator/OAuthAuthenticator.js +31 -0
- package/build/core/dataGenerator/authenticator/OrgOAuthAuthenticator.js +29 -0
- package/build/core/playwright/builtInFixtures/context.js +3 -36
- package/build/core/playwright/readConfigFile.js +1 -7
- package/build/core/playwright/setup/custom-reporter.js +1 -87
- package/build/test/core/dataGenerator/authenticator/__test__/Authenticator.test.js +89 -0
- package/build/test/core/dataGenerator/authenticator/__test__/JWTauthenticator.test.js +134 -0
- package/build/test/core/dataGenerator/authenticator/__test__/OAuthAuthenticator.test.js +46 -0
- package/build/test/core/dataGenerator/authenticator/__test__/OrgOAuthAuthenticator.test.js +46 -0
- package/build/test/core/dataGenerator/authenticator/__tests__/JWTauthenticator.test.js +165 -0
- package/jest.config.js +1 -1
- package/npm-shrinkwrap.json +4 -3
- package/package.json +2 -2
- package/unit_reports/unit-report.html +1 -1
- package/build/core/playwright/helpers/logCollector.js +0 -153
|
@@ -4,16 +4,20 @@ import { generateAndCacheTestData } from './DataGeneratorStepsHelper';
|
|
|
4
4
|
const { Given } = createBdd();
|
|
5
5
|
|
|
6
6
|
Given('generate a {string} entity {string} with generator {string}', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, generatorName, dataTable) => {
|
|
7
|
-
await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName);
|
|
7
|
+
await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, null, page);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
Given('generate a {string} entity {string} with API {string}', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, dataTable) => {
|
|
11
|
-
await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName);
|
|
11
|
+
await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, null, page);
|
|
12
12
|
});
|
|
13
13
|
Given('generate a {string} entity {string} with generator {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, generatorName, profile, dataTable) => {
|
|
14
|
-
await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, profile);
|
|
14
|
+
await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, profile, page);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
Given('generate a {string} entity {string} with API {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, profile, dataTable) => {
|
|
18
|
-
await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile);
|
|
18
|
+
await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile, page);
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
Given('generate a {string} entity {string} with API {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, profile, dataTable) => {
|
|
22
|
+
await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile, page);
|
|
23
|
+
});
|
|
@@ -4,7 +4,7 @@ import DataGenerator from '@zohodesk/testinglibrary/DataGenerator';
|
|
|
4
4
|
|
|
5
5
|
const dataGenerator = new DataGenerator();
|
|
6
6
|
|
|
7
|
-
export async function generateAndCacheTestData(executionContext, type, identifier, dataTable, cacheLayer, entityName, profile = null) {
|
|
7
|
+
export async function generateAndCacheTestData(executionContext, type, identifier, dataTable, cacheLayer, entityName, profile = null, page = null) {
|
|
8
8
|
let actorInfo;
|
|
9
9
|
const testInfo = test.info();
|
|
10
10
|
const scenarioName = testInfo.title.split('/').pop() || 'Unknown Scenario';
|
|
@@ -16,6 +16,6 @@ export async function generateAndCacheTestData(executionContext, type, identifie
|
|
|
16
16
|
actorInfo = executionContext.actorInfo;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const generatedData = await dataGenerator.generate(testInfo, actorInfo, type, identifier, scenarioName, dataTable ? dataTable.hashes() : []);
|
|
19
|
+
const generatedData = await dataGenerator.generate(testInfo, actorInfo, type, identifier, scenarioName, dataTable ? dataTable.hashes() : [], page);
|
|
20
20
|
await cacheLayer.set(entityName, generatedData.data);
|
|
21
21
|
}
|
|
@@ -9,6 +9,7 @@ var _path = _interopRequireDefault(require("path"));
|
|
|
9
9
|
var _fs = _interopRequireDefault(require("fs"));
|
|
10
10
|
var _logger = require("../../utils/logger");
|
|
11
11
|
var _DataGeneratorHelper = require("./DataGeneratorHelper");
|
|
12
|
+
var _Authenticator = _interopRequireDefault(require("./authenticator/Authenticator.js"));
|
|
12
13
|
var _helpers = require("@zohodesk/testinglibrary/helpers");
|
|
13
14
|
var _DataGeneratorError = require("./DataGeneratorError");
|
|
14
15
|
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
|
|
@@ -19,7 +20,7 @@ class DataGenerator {
|
|
|
19
20
|
constructor() {
|
|
20
21
|
_classPrivateMethodInitSpec(this, _DataGenerator_brand);
|
|
21
22
|
}
|
|
22
|
-
async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable) {
|
|
23
|
+
async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable, page = null) {
|
|
23
24
|
try {
|
|
24
25
|
let generators;
|
|
25
26
|
if (generatorType === 'API') {
|
|
@@ -28,7 +29,7 @@ class DataGenerator {
|
|
|
28
29
|
generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this, testInfo, generatorName);
|
|
29
30
|
}
|
|
30
31
|
const processedGenerators = await (0, _DataGeneratorHelper.processGenerator)(generators, dataTable);
|
|
31
|
-
const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo);
|
|
32
|
+
const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo, page);
|
|
32
33
|
const response = await (0, _DataGeneratorHelper.makeRequest)(process.env.DG_SERVICE_DOMAIN + process.env.DG_SERVICE_API_PATH, apiPayload);
|
|
33
34
|
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Generated response for the generator: ${generatorName} for scenario: ${scenarioName}, Response: ${JSON.stringify(response)}`);
|
|
34
35
|
return response;
|
|
@@ -72,12 +73,12 @@ async function _generateAPIGenerator(operationId) {
|
|
|
72
73
|
name: operationId
|
|
73
74
|
}];
|
|
74
75
|
}
|
|
75
|
-
async function _constructApiPayload(scenarioName, processedGenerators, actorInfo) {
|
|
76
|
+
async function _constructApiPayload(scenarioName, processedGenerators, actorInfo, page = null) {
|
|
76
77
|
const dataGeneratorObj = actorInfo['data-generator'];
|
|
77
78
|
if (!dataGeneratorObj) {
|
|
78
79
|
throw new _DataGeneratorError.DataGeneratorConfigurationError(`Data Generator configuration is missing for the profile: ${actorInfo['profile']}`);
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
let apiPayload = {
|
|
81
82
|
scenario_name: scenarioName,
|
|
82
83
|
data_generation_templates: processedGenerators,
|
|
83
84
|
...dataGeneratorObj
|
|
@@ -94,6 +95,10 @@ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo
|
|
|
94
95
|
environmentDetails.host = domainUrl.origin;
|
|
95
96
|
}
|
|
96
97
|
apiPayload.environmentDetails = environmentDetails;
|
|
98
|
+
|
|
99
|
+
// Only perform auth augmentation via Authenticator
|
|
100
|
+
apiPayload = await new _Authenticator.default().constructPayload(apiPayload, actorInfo, page);
|
|
101
|
+
console.log("Final API Payload after authentication augmentation: ", JSON.stringify(apiPayload));
|
|
97
102
|
return apiPayload;
|
|
98
103
|
}
|
|
99
104
|
var _default = exports.default = DataGenerator;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
8
|
+
class Authenticator {
|
|
9
|
+
// Only route authentication augmentations; do not modify environment or base payload here.
|
|
10
|
+
async constructPayload(apiPayload, actorInfo, page = null) {
|
|
11
|
+
var _actorInfo$dataGener;
|
|
12
|
+
const authenticationLoaders = {
|
|
13
|
+
'jwt-auth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./JWTauthenticator.js'))),
|
|
14
|
+
oauth2: () => Promise.resolve().then(() => _interopRequireWildcard(require('./OAuthAuthenticator.js'))),
|
|
15
|
+
'org-oauth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./OrgOAuthAuthenticator.js'))),
|
|
16
|
+
'cookie-auth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./CookieAuthenticator.js')))
|
|
17
|
+
};
|
|
18
|
+
const authentications = (actorInfo === null || actorInfo === void 0 || (_actorInfo$dataGener = actorInfo['data-generator']) === null || _actorInfo$dataGener === void 0 || (_actorInfo$dataGener = _actorInfo$dataGener.account) === null || _actorInfo$dataGener === void 0 ? void 0 : _actorInfo$dataGener.authentications) || [];
|
|
19
|
+
const authenticationTypes = [...new Set(authentications.map(auth => String(auth).toLowerCase()).filter(Boolean))];
|
|
20
|
+
for (const authenticationType of authenticationTypes) {
|
|
21
|
+
const authenticatorLoader = authenticationLoaders[authenticationType];
|
|
22
|
+
if (!authenticatorLoader) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const {
|
|
26
|
+
default: AuthenticatorClass
|
|
27
|
+
} = await authenticatorLoader();
|
|
28
|
+
const authenticatorInstance = new AuthenticatorClass();
|
|
29
|
+
apiPayload = await authenticatorInstance.constructPayload(apiPayload, actorInfo, page);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return apiPayload;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.default = Authenticator;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
|
|
9
|
+
/**
|
|
10
|
+
* CookieAuthenticator prepares the payload for cookie-based authentication.
|
|
11
|
+
* It extracts cookies from the browser page context and attaches them
|
|
12
|
+
* as cookieAuthHeaders in the account block for the data generator service.
|
|
13
|
+
*/
|
|
14
|
+
class CookieAuthenticator extends _Authenticator.default {
|
|
15
|
+
async constructPayload(apiPayload, actorInfo, page = null) {
|
|
16
|
+
apiPayload.account = apiPayload.account || {};
|
|
17
|
+
if (page) {
|
|
18
|
+
const {
|
|
19
|
+
crmcsrToken,
|
|
20
|
+
cookieString
|
|
21
|
+
} = await _getCookies.call(CookieAuthenticator, page);
|
|
22
|
+
apiPayload.account.authHeaders = {
|
|
23
|
+
Cookie: cookieString,
|
|
24
|
+
'X-ZCSRF-TOKEN': crmcsrToken
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return apiPayload;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function _getCookies(page) {
|
|
31
|
+
const context = page.context();
|
|
32
|
+
const pageUrl = new URL(page.url());
|
|
33
|
+
const currentDomain = pageUrl.hostname;
|
|
34
|
+
const cookies = await context.cookies([page.url()]);
|
|
35
|
+
const crmcsrCookie = cookies.filter(cookie => cookie.domain.includes(currentDomain) && cookie.name === 'crmcsr');
|
|
36
|
+
return {
|
|
37
|
+
crmcsrToken: crmcsrCookie.map(c => `crmcsrfparam=${c.value}`).join('; '),
|
|
38
|
+
cookieString: cookies.map(c => `${c.name}=${c.value}`).join('; ')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var _default = exports.default = CookieAuthenticator;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
|
|
9
|
+
/**
|
|
10
|
+
* JWTauthenticator constructs payload by fetching a JWT access token
|
|
11
|
+
* from the authenticator app and attaching it to the account.jwt config.
|
|
12
|
+
*/
|
|
13
|
+
class JWTauthenticator extends _Authenticator.default {
|
|
14
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
15
|
+
const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
|
|
16
|
+
const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
|
|
17
|
+
const jwtConf = account === null || account === void 0 ? void 0 : account['jwt-oauth'];
|
|
18
|
+
|
|
19
|
+
// If jwt config is not present, nothing to do
|
|
20
|
+
if (!jwtConf) {
|
|
21
|
+
return apiPayload;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Build payload for authenticator service
|
|
25
|
+
const userToken = encodeURIComponent((actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo.email) || '');
|
|
26
|
+
const jwtAuthUrl = process.env.DG_JWT_AUTH_URL;
|
|
27
|
+
const responseType = jwtConf.response_type;
|
|
28
|
+
const clientId = jwtConf.client_id;
|
|
29
|
+
|
|
30
|
+
// Resolve token_url from config or settings template
|
|
31
|
+
let tokenUrl = jwtConf.token_url;
|
|
32
|
+
if (!tokenUrl && process.env.DG_JWT_TOKEN_URL && clientId && clientId.includes('.')) {
|
|
33
|
+
const zsoid = clientId.split('.')[0];
|
|
34
|
+
tokenUrl = process.env.DG_JWT_TOKEN_URL.replace('{ZSOID}', zsoid);
|
|
35
|
+
}
|
|
36
|
+
const domainUrl = jwtConf.domain_url || process.env.DG_DOMAIN_URL;
|
|
37
|
+
const authPayload = {
|
|
38
|
+
user_token: userToken,
|
|
39
|
+
jwt_secret: jwtConf.jwt_secret,
|
|
40
|
+
jwt_auth_url: jwtAuthUrl,
|
|
41
|
+
response_type: responseType,
|
|
42
|
+
client_id: clientId,
|
|
43
|
+
token_url: tokenUrl,
|
|
44
|
+
domain_url: domainUrl
|
|
45
|
+
};
|
|
46
|
+
const authenticatorApi = process.env.DG_JWT_AUTHENTICATOR_API;
|
|
47
|
+
const resp = await fetch(authenticatorApi, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json'
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify(authPayload)
|
|
53
|
+
});
|
|
54
|
+
if (!resp.ok) {
|
|
55
|
+
const errorBody = await resp.text();
|
|
56
|
+
throw new Error(`JWT authenticator error! status: ${resp.status}, body: ${errorBody}`);
|
|
57
|
+
}
|
|
58
|
+
const json = await resp.json();
|
|
59
|
+
const accessToken = json === null || json === void 0 ? void 0 : json.access_token;
|
|
60
|
+
if (!accessToken) {
|
|
61
|
+
throw new Error('JWT authenticator did not return access_token');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Attach access token to authHeaders so downstream API calls can use it directly
|
|
65
|
+
apiPayload.account = apiPayload.account || {};
|
|
66
|
+
apiPayload.account.authHeaders = apiPayload.account.authHeaders || {};
|
|
67
|
+
apiPayload.account.authHeaders.Authorization = `Zoho-oauthtoken ${accessToken}`;
|
|
68
|
+
return apiPayload;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var _default = exports.default = JWTauthenticator;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
|
|
9
|
+
/**
|
|
10
|
+
* OAuthAuthenticator prepares the payload for OAuth2-based authentication.
|
|
11
|
+
* It ensures oauth2 config is present in the account block so the
|
|
12
|
+
* data generator service can use it.
|
|
13
|
+
*/
|
|
14
|
+
class OAuthAuthenticator extends _Authenticator.default {
|
|
15
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
16
|
+
const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
|
|
17
|
+
const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
|
|
18
|
+
|
|
19
|
+
// If oauth2 details exist in actor info, merge them into payload
|
|
20
|
+
const oauth2 = account === null || account === void 0 ? void 0 : account.oauth2;
|
|
21
|
+
apiPayload.account = apiPayload.account || {};
|
|
22
|
+
if (oauth2) {
|
|
23
|
+
apiPayload.account.oauth2 = {
|
|
24
|
+
...(apiPayload.account.oauth2 || {}),
|
|
25
|
+
...oauth2
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return apiPayload;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
var _default = exports.default = OAuthAuthenticator;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
|
|
9
|
+
/**
|
|
10
|
+
* OrgOAuthAuthenticator prepares the payload for org_oauth authentication.
|
|
11
|
+
* It ensures org_oauth config is present in the account block so the
|
|
12
|
+
* data generator service can use it.
|
|
13
|
+
*/
|
|
14
|
+
class OrgOAuthAuthenticator extends _Authenticator.default {
|
|
15
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
16
|
+
const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
|
|
17
|
+
const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
|
|
18
|
+
const orgOauth = account === null || account === void 0 ? void 0 : account.org_oauth;
|
|
19
|
+
apiPayload.account = apiPayload.account || {};
|
|
20
|
+
if (orgOauth) {
|
|
21
|
+
apiPayload.account.org_oauth = {
|
|
22
|
+
...(apiPayload.account.org_oauth || {}),
|
|
23
|
+
...orgOauth
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return apiPayload;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
var _default = exports.default = OrgOAuthAuthenticator;
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
3
|
Object.defineProperty(exports, "__esModule", {
|
|
5
4
|
value: true
|
|
6
5
|
});
|
|
7
6
|
exports.default = void 0;
|
|
8
|
-
var _path = _interopRequireDefault(require("path"));
|
|
9
7
|
var _readConfigFile = require("../readConfigFile");
|
|
10
|
-
var _logCollector = require("../helpers/logCollector");
|
|
11
8
|
const {
|
|
12
|
-
testSetup
|
|
13
|
-
uatDirectory,
|
|
14
|
-
runtimeLogs = {}
|
|
9
|
+
testSetup
|
|
15
10
|
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
16
|
-
|
|
17
|
-
// Keep runtime logs OUTSIDE reportPath (HTML reporter wipes it on onEnd) and
|
|
18
|
-
// outside test-results (Playwright outputDir is cleaned each run).
|
|
19
|
-
const LOGS_DIR = _path.default.join(uatDirectory, 'runtime-logs');
|
|
20
11
|
async function performDefaultContextSteps({
|
|
21
12
|
context
|
|
22
13
|
}) {
|
|
@@ -29,37 +20,13 @@ async function performDefaultContextSteps({
|
|
|
29
20
|
var _default = exports.default = {
|
|
30
21
|
context: async ({
|
|
31
22
|
context
|
|
32
|
-
}, use
|
|
23
|
+
}, use) => {
|
|
33
24
|
await context.addInitScript(() =>
|
|
34
25
|
// eslint-disable-next-line no-undef
|
|
35
26
|
window.localStorage.setItem('isDnBannerHide', true));
|
|
36
27
|
await performDefaultContextSteps({
|
|
37
28
|
context
|
|
38
29
|
});
|
|
39
|
-
|
|
40
|
-
collector.attachToContext(context);
|
|
41
|
-
testInfo.runtimeLogs = collector;
|
|
42
|
-
try {
|
|
43
|
-
await use(context);
|
|
44
|
-
} finally {
|
|
45
|
-
collector.detach();
|
|
46
|
-
const failureOnly = runtimeLogs.mode === 'failure-only';
|
|
47
|
-
const isFailure = testInfo.status && testInfo.status !== testInfo.expectedStatus;
|
|
48
|
-
const shouldFlush = !failureOnly || isFailure;
|
|
49
|
-
const summary = collector.toSummary();
|
|
50
|
-
let filePath = null;
|
|
51
|
-
if (shouldFlush) {
|
|
52
|
-
const base = `w${testInfo.workerIndex}-${testInfo.testId}-${testInfo.retry}`;
|
|
53
|
-
filePath = collector.flushToFile(LOGS_DIR, base);
|
|
54
|
-
}
|
|
55
|
-
await testInfo.attach('runtime-log', {
|
|
56
|
-
contentType: 'application/json',
|
|
57
|
-
body: Buffer.from(JSON.stringify({
|
|
58
|
-
schemaVersion: 1,
|
|
59
|
-
summary,
|
|
60
|
-
file: filePath
|
|
61
|
-
}))
|
|
62
|
-
});
|
|
63
|
-
}
|
|
30
|
+
await use(context);
|
|
64
31
|
}
|
|
65
32
|
};
|
|
@@ -55,11 +55,7 @@ function getDefaultConfig() {
|
|
|
55
55
|
featureFilesFolder: 'feature-files',
|
|
56
56
|
stepDefinitionsFolder: 'steps',
|
|
57
57
|
testSetup: {},
|
|
58
|
-
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
|
|
59
|
-
showCaseTimings: true,
|
|
60
|
-
runtimeLogs: {
|
|
61
|
-
mode: 'failure-only'
|
|
62
|
-
}
|
|
58
|
+
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
|
|
63
59
|
};
|
|
64
60
|
}
|
|
65
61
|
function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
@@ -117,8 +113,6 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
|
117
113
|
* @property {string} testIdAttribute: Change the default data-testid attribute. configure what attribute to search while calling getByTestId
|
|
118
114
|
* @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
|
|
119
115
|
* @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
|
|
120
|
-
* @property {boolean} showCaseTimings: When true, the console reporter prints per-case start/end wall-clock times (h:mm:ss AM/PM) and a case-timings.json sidecar is written to reportPath for the HTML report addon. Default: true.
|
|
121
|
-
* @property {Object} runtimeLogs: Runtime browser log capture. { mode: 'failure-only' | 'always', limits?: { maxEvents, captureHeaders, ignoreUrlPatterns } }. Default: { mode: 'failure-only' }.
|
|
122
116
|
*/
|
|
123
117
|
|
|
124
118
|
/**
|
|
@@ -12,22 +12,6 @@ var _readConfigFile = require("../readConfigFile");
|
|
|
12
12
|
var _logger = require("../../../utils/logger");
|
|
13
13
|
var _configFileNameProvider = require("../helpers/configFileNameProvider");
|
|
14
14
|
var _mergeAbortedTests = _interopRequireDefault(require("../reporter/helpers/mergeAbortedTests"));
|
|
15
|
-
function formatTime(date) {
|
|
16
|
-
return date.toLocaleTimeString('en-US', {
|
|
17
|
-
hour: 'numeric',
|
|
18
|
-
minute: '2-digit',
|
|
19
|
-
second: '2-digit',
|
|
20
|
-
hour12: true
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function formatDuration(ms) {
|
|
24
|
-
if (ms < 1000) return `${ms}ms`;
|
|
25
|
-
const totalSeconds = ms / 1000;
|
|
26
|
-
if (totalSeconds < 60) return `${totalSeconds.toFixed(2)}s`;
|
|
27
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
28
|
-
const seconds = (totalSeconds - minutes * 60).toFixed(2);
|
|
29
|
-
return `${minutes}m ${seconds}s`;
|
|
30
|
-
}
|
|
31
15
|
class JSONSummaryReporter {
|
|
32
16
|
constructor() {
|
|
33
17
|
this.durationInMS = -1;
|
|
@@ -42,22 +26,11 @@ class JSONSummaryReporter {
|
|
|
42
26
|
this.failedSteps = [];
|
|
43
27
|
this.status = 'unknown';
|
|
44
28
|
this.startedAt = 0;
|
|
45
|
-
|
|
46
|
-
this._open = config.openReportOn;
|
|
47
|
-
this._showCaseTimings = config.showCaseTimings !== false;
|
|
48
|
-
this._caseTimings = [];
|
|
29
|
+
this._open = (0, _readConfigFile.generateConfigFromFile)().openReportOn;
|
|
49
30
|
}
|
|
50
31
|
onBegin() {
|
|
51
32
|
this.startedAt = Date.now();
|
|
52
33
|
}
|
|
53
|
-
onTestBegin(test) {
|
|
54
|
-
if (!this._showCaseTimings) return;
|
|
55
|
-
const {
|
|
56
|
-
fullTitle
|
|
57
|
-
} = this.getTitle(test);
|
|
58
|
-
const startedAtLabel = formatTime(new Date());
|
|
59
|
-
_logger.Logger.log(_logger.Logger.INFO_TYPE, `▶ ${fullTitle} — started at ${startedAtLabel}`);
|
|
60
|
-
}
|
|
61
34
|
getTitle(test) {
|
|
62
35
|
const title = [];
|
|
63
36
|
const fileName = [];
|
|
@@ -103,55 +76,6 @@ class JSONSummaryReporter {
|
|
|
103
76
|
this[status].push(fileName);
|
|
104
77
|
}
|
|
105
78
|
this[status].push(fileName);
|
|
106
|
-
if (this._showCaseTimings && result.startTime) {
|
|
107
|
-
const startDate = new Date(result.startTime);
|
|
108
|
-
const endDate = new Date(startDate.getTime() + (result.duration || 0));
|
|
109
|
-
const startLabel = formatTime(startDate);
|
|
110
|
-
const endLabel = formatTime(endDate);
|
|
111
|
-
const durationLabel = formatDuration(result.duration || 0);
|
|
112
|
-
const statusGlyph = result.status === 'passed' ? '✓' : result.status === 'skipped' ? '○' : '✗';
|
|
113
|
-
const logType = result.status === 'passed' ? _logger.Logger.SUCCESS_TYPE : result.status === 'skipped' ? _logger.Logger.INFO_TYPE : _logger.Logger.FAILURE_TYPE;
|
|
114
|
-
_logger.Logger.log(logType, `${statusGlyph} ${fullTitle} — ended at ${endLabel} (started ${startLabel}, took ${durationLabel})`);
|
|
115
|
-
const isFailure = result.status !== 'passed' && result.status !== 'skipped';
|
|
116
|
-
if (isFailure) {
|
|
117
|
-
var _result$error;
|
|
118
|
-
const runtimeAttachment = (result.attachments || []).find(a => a.name === 'runtime-log' && a.body);
|
|
119
|
-
let runtime = null;
|
|
120
|
-
if (runtimeAttachment) {
|
|
121
|
-
try {
|
|
122
|
-
runtime = JSON.parse(runtimeAttachment.body.toString('utf8'));
|
|
123
|
-
} catch {
|
|
124
|
-
// malformed attachment; skip
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
this._caseTimings.push({
|
|
128
|
-
title: fullTitle,
|
|
129
|
-
fileName,
|
|
130
|
-
status: result.status,
|
|
131
|
-
retry: result.retry,
|
|
132
|
-
startTime: startDate.toISOString(),
|
|
133
|
-
endTime: endDate.toISOString(),
|
|
134
|
-
startTimeFormatted: startLabel,
|
|
135
|
-
endTimeFormatted: endLabel,
|
|
136
|
-
duration: result.duration || 0,
|
|
137
|
-
durationFormatted: durationLabel,
|
|
138
|
-
errorMessage: (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.message,
|
|
139
|
-
failedSteps: (result.steps || []).filter(step => step.error).map(step => {
|
|
140
|
-
var _step$error;
|
|
141
|
-
return {
|
|
142
|
-
title: step.title,
|
|
143
|
-
error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
|
|
144
|
-
};
|
|
145
|
-
}),
|
|
146
|
-
...(runtime ? {
|
|
147
|
-
runtime: {
|
|
148
|
-
summary: runtime.summary,
|
|
149
|
-
logFile: runtime.file
|
|
150
|
-
}
|
|
151
|
-
} : {})
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
79
|
}
|
|
156
80
|
onError(error) {
|
|
157
81
|
this.errored.push({
|
|
@@ -198,16 +122,6 @@ class JSONSummaryReporter {
|
|
|
198
122
|
reportPath
|
|
199
123
|
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
200
124
|
(0, _fileUtils.writeFileContents)(_path.default.join(reportPath, './', (0, _configFileNameProvider.getReportFileName)()), JSON.stringify(this, null, ' '));
|
|
201
|
-
if (this._showCaseTimings && this._caseTimings.length > 0) {
|
|
202
|
-
const timingsPayload = {
|
|
203
|
-
suiteStartedAt: new Date(this.startedAt).toISOString(),
|
|
204
|
-
suiteEndedAt: new Date(this.startedAt + this.durationInMS).toISOString(),
|
|
205
|
-
suiteDurationMs: this.durationInMS,
|
|
206
|
-
failedCount: this._caseTimings.length,
|
|
207
|
-
cases: this._caseTimings
|
|
208
|
-
};
|
|
209
|
-
(0, _fileUtils.writeFileContents)(_path.default.join(reportPath, 'case-timings.json'), JSON.stringify(timingsPayload, null, ' '));
|
|
210
|
-
}
|
|
211
125
|
}
|
|
212
126
|
onExit() {
|
|
213
127
|
// Update .last-run.json with aborted tests due to timing out or interruption
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _Authenticator = _interopRequireDefault(require("../../../../../../src/core/dataGenerator/authenticator/Authenticator"));
|
|
5
|
+
describe('Authenticator', () => {
|
|
6
|
+
it('should return unchanged payload when no supported authentication type exists', async () => {
|
|
7
|
+
const authenticator = new _Authenticator.default();
|
|
8
|
+
const apiPayload = {
|
|
9
|
+
account: {
|
|
10
|
+
name: 'sample'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const actorInfo = {
|
|
14
|
+
'data-generator': {
|
|
15
|
+
account: {
|
|
16
|
+
authentications: ['apikey', 'basic']
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
21
|
+
expect(result).toEqual(apiPayload);
|
|
22
|
+
});
|
|
23
|
+
it('should route oauth2 authentication and merge oauth2 config into payload', async () => {
|
|
24
|
+
const authenticator = new _Authenticator.default();
|
|
25
|
+
const apiPayload = {
|
|
26
|
+
account: {}
|
|
27
|
+
};
|
|
28
|
+
const actorInfo = {
|
|
29
|
+
'data-generator': {
|
|
30
|
+
account: {
|
|
31
|
+
authentications: ['oauth2'],
|
|
32
|
+
oauth2: {
|
|
33
|
+
client_id: 'client_1',
|
|
34
|
+
client_secret: 'secret_1'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
40
|
+
expect(result.account.oauth2).toEqual({
|
|
41
|
+
client_id: 'client_1',
|
|
42
|
+
client_secret: 'secret_1'
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('should treat authentication values case-insensitively and deduplicate them', async () => {
|
|
46
|
+
const authenticator = new _Authenticator.default();
|
|
47
|
+
const apiPayload = {
|
|
48
|
+
account: {}
|
|
49
|
+
};
|
|
50
|
+
const actorInfo = {
|
|
51
|
+
'data-generator': {
|
|
52
|
+
account: {
|
|
53
|
+
authentications: ['OAUTH2', 'oauth2', 'OAuth2'],
|
|
54
|
+
oauth2: {
|
|
55
|
+
scope: 'read'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
61
|
+
expect(result.account.oauth2).toEqual({
|
|
62
|
+
scope: 'read'
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('should apply only the first supported authentication type', async () => {
|
|
66
|
+
const authenticator = new _Authenticator.default();
|
|
67
|
+
const apiPayload = {
|
|
68
|
+
account: {}
|
|
69
|
+
};
|
|
70
|
+
const actorInfo = {
|
|
71
|
+
'data-generator': {
|
|
72
|
+
account: {
|
|
73
|
+
authentications: ['oauth2', 'org-oauth'],
|
|
74
|
+
oauth2: {
|
|
75
|
+
client_id: 'oauth-client'
|
|
76
|
+
},
|
|
77
|
+
org_oauth: {
|
|
78
|
+
org_id: 'org-101'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
84
|
+
expect(result.account.oauth2).toEqual({
|
|
85
|
+
client_id: 'oauth-client'
|
|
86
|
+
});
|
|
87
|
+
expect(result.account.org_oauth).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|