@zohodesk/testinglibrary 0.0.74-n20-experimental → 0.0.81-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/README.md +21 -0
- package/build/common/data-generator/steps/DataGenerator.spec.js +5 -5
- package/build/common/data-generator/steps/DataGeneratorStepsHelper.js +2 -2
- package/build/core/dataGenerator/DataGenerator.js +16 -6
- package/build/core/dataGenerator/DataGeneratorError.js +12 -2
- package/build/core/dataGenerator/authenticator/Authenticator.js +37 -0
- package/build/core/dataGenerator/authenticator/CookieAuthenticator.js +48 -0
- package/build/core/dataGenerator/authenticator/JWTauthenticator.js +64 -0
- package/build/core/dataGenerator/authenticator/OAuthAuthenticator.js +26 -0
- package/build/core/dataGenerator/authenticator/OrgOAuthAuthenticator.js +26 -0
- package/build/core/dataGenerator/authenticator/cookieAuthConfig.js +11 -0
- package/build/core/playwright/builtInFixtures/actorContext.js +13 -10
- package/build/core/playwright/builtInFixtures/context.js +42 -3
- package/build/core/playwright/builtInFixtures/page.js +0 -1
- package/build/core/playwright/helpers/logCollector.js +167 -0
- package/build/core/playwright/readConfigFile.js +9 -1
- package/build/core/playwright/setup/ProjectConfiguration.js +3 -1
- package/build/core/playwright/setup/custom-reporter.js +87 -1
- package/build/core/playwright/types.js +1 -0
- package/build/setup-folder-structure/samples/uat-config-sample.js +2 -0
- package/build/test/core/dataGenerator/authenticator/__tests__/Authenticator.test.js +88 -0
- package/build/test/core/dataGenerator/authenticator/__tests__/JWTauthenticator.test.js +129 -0
- package/build/test/core/dataGenerator/authenticator/__tests__/OAuthAuthenticator.test.js +39 -0
- package/build/test/core/dataGenerator/authenticator/__tests__/OrgOAuthAuthenticator.test.js +39 -0
- package/build/test/core/playwright/__tests__/validateFeature.test.js +4 -1
- package/build/test/core/playwright/runner/__tests__/RunnerHelper.test.js +3 -0
- package/build/test/core/playwright/runner/__tests__/SpawnRunner.test.js +3 -0
- package/npm-shrinkwrap.json +1194 -1205
- package/package.json +4 -4
- package/unit_reports/unit-report.html +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,27 @@
|
|
|
17
17
|
|
|
18
18
|
- npm run report
|
|
19
19
|
|
|
20
|
+
### v4.1.9/v3.3.5 - 03-07-2026
|
|
21
|
+
|
|
22
|
+
#### Feature
|
|
23
|
+
- PortalDomain option provided in the settings.json.
|
|
24
|
+
- This lets customer portal and related UAT flows resolve the portal URL from configuration instead of hardcoding environment-specific links.
|
|
25
|
+
- It simplifies switching between setups such as dev, QA, and UAT without changing test code.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### v4.1.5/v3.3.3 - 19-06-2026
|
|
29
|
+
|
|
30
|
+
#### Refactor
|
|
31
|
+
- Authenticator module refactored to use static imports instead of dynamic imports
|
|
32
|
+
- Removed unnecessary `extends Authenticator` from concrete authenticator classes (`JWTauthenticator`, `OAuthAuthenticator`, `OrgOAuthAuthenticator`, `CookieAuthenticator`) to eliminate circular dependency
|
|
33
|
+
- Unit test cases updated to align with actual source behavior
|
|
34
|
+
|
|
35
|
+
### v4.1.4/v3.3.3 - 18-06-2026
|
|
36
|
+
|
|
37
|
+
#### Bugfix
|
|
38
|
+
- Dependency project failure stats pushed into custom reporter
|
|
39
|
+
- `stepDefinitionsFolder` hardcoded changes removed
|
|
40
|
+
|
|
20
41
|
### v4.1.3/v3.3.2 - 08-06-2026
|
|
21
42
|
|
|
22
43
|
#### Features
|
|
@@ -4,16 +4,16 @@ 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, type: "template", identifier: generatorName, dataTable, cacheLayer, entityName, 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, type: "API", identifier: operationId, dataTable, cacheLayer, entityName, 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, type: "template", identifier: 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);
|
|
19
|
-
});
|
|
18
|
+
await generateAndCacheTestData({ executionContext, type: "API", identifier: operationId, dataTable, cacheLayer, entityName, profile, page });
|
|
19
|
+
});
|
|
@@ -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,16 +20,21 @@ 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;
|
|
26
|
+
let domainUrl;
|
|
25
27
|
if (generatorType === 'API') {
|
|
26
28
|
generators = await _assertClassBrand(_DataGenerator_brand, this, _generateAPIGenerator).call(this, generatorName);
|
|
27
29
|
} else {
|
|
28
30
|
generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this, testInfo, generatorName);
|
|
29
31
|
}
|
|
32
|
+
const generatorOperationIds = generators.map(({
|
|
33
|
+
generatorOperationId
|
|
34
|
+
}) => generatorOperationId).filter(Boolean);
|
|
35
|
+
domainUrl = generatorOperationIds.some(operationId => operationId.startsWith('portal.')) ? process.env.portalDomain : process.env.domain;
|
|
30
36
|
const processedGenerators = await (0, _DataGeneratorHelper.processGenerator)(generators, dataTable);
|
|
31
|
-
const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo);
|
|
37
|
+
const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo, domainUrl, page);
|
|
32
38
|
const response = await (0, _DataGeneratorHelper.makeRequest)(process.env.DG_SERVICE_DOMAIN + process.env.DG_SERVICE_API_PATH, apiPayload);
|
|
33
39
|
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Generated response for the generator: ${generatorName} for scenario: ${scenarioName}, Response: ${JSON.stringify(response)}`);
|
|
34
40
|
return response;
|
|
@@ -72,12 +78,12 @@ async function _generateAPIGenerator(operationId) {
|
|
|
72
78
|
name: operationId
|
|
73
79
|
}];
|
|
74
80
|
}
|
|
75
|
-
async function _constructApiPayload(scenarioName, processedGenerators, actorInfo) {
|
|
81
|
+
async function _constructApiPayload(scenarioName, processedGenerators, actorInfo, domainUrl, page = null) {
|
|
76
82
|
const dataGeneratorObj = actorInfo['data-generator'];
|
|
77
83
|
if (!dataGeneratorObj) {
|
|
78
84
|
throw new _DataGeneratorError.DataGeneratorConfigurationError(`Data Generator configuration is missing for the profile: ${actorInfo['profile']}`);
|
|
79
85
|
}
|
|
80
|
-
|
|
86
|
+
let apiPayload = {
|
|
81
87
|
scenario_name: scenarioName,
|
|
82
88
|
data_generation_templates: processedGenerators,
|
|
83
89
|
...dataGeneratorObj
|
|
@@ -90,10 +96,14 @@ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo
|
|
|
90
96
|
const environmentDetails = apiPayload.environmentDetails || {};
|
|
91
97
|
if (environmentDetails) {
|
|
92
98
|
environmentDetails.iam_url = process.env.DG_IAM_DOMAIN;
|
|
93
|
-
const
|
|
94
|
-
environmentDetails.host =
|
|
99
|
+
const selectedDomain = new URL(domainUrl || process.env.domain);
|
|
100
|
+
environmentDetails.host = selectedDomain.origin;
|
|
95
101
|
}
|
|
96
102
|
apiPayload.environmentDetails = environmentDetails;
|
|
103
|
+
|
|
104
|
+
// Only perform auth augmentation via Authenticator
|
|
105
|
+
apiPayload = await new _Authenticator.default().constructPayload(apiPayload, actorInfo, page);
|
|
106
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Final API Payload after authentication augmentation: ${JSON.stringify(apiPayload)}`);
|
|
97
107
|
return apiPayload;
|
|
98
108
|
}
|
|
99
109
|
var _default = exports.default = DataGenerator;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.GeneratorError = exports.DataGeneratorError = exports.DataGeneratorConfigurationError = void 0;
|
|
6
|
+
exports.GeneratorError = exports.DataGeneratorError = exports.DataGeneratorConfigurationError = exports.AuthenticationError = void 0;
|
|
7
7
|
class DataGeneratorError extends Error {
|
|
8
8
|
constructor(message) {
|
|
9
9
|
super(message);
|
|
@@ -47,4 +47,14 @@ class GeneratorError extends DataGeneratorError {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
exports.GeneratorError = GeneratorError;
|
|
50
|
+
exports.GeneratorError = GeneratorError;
|
|
51
|
+
class AuthenticationError extends DataGeneratorError {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = 'AuthenticationError';
|
|
55
|
+
if (Error.captureStackTrace) {
|
|
56
|
+
Error.captureStackTrace(this, AuthenticationError);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.AuthenticationError = AuthenticationError;
|
|
@@ -0,0 +1,37 @@
|
|
|
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 _DataGeneratorError = require("../DataGeneratorError.js");
|
|
9
|
+
var _JWTauthenticator = _interopRequireDefault(require("./JWTauthenticator.js"));
|
|
10
|
+
var _OAuthAuthenticator = _interopRequireDefault(require("./OAuthAuthenticator.js"));
|
|
11
|
+
var _OrgOAuthAuthenticator = _interopRequireDefault(require("./OrgOAuthAuthenticator.js"));
|
|
12
|
+
var _CookieAuthenticator = _interopRequireDefault(require("./CookieAuthenticator.js"));
|
|
13
|
+
class Authenticator {
|
|
14
|
+
// Only route authentication augmentations; do not modify environment or base payload here.
|
|
15
|
+
async constructPayload(apiPayload, actorInfo, page = null) {
|
|
16
|
+
var _actorInfo$dataGener;
|
|
17
|
+
const authenticationLoaders = {
|
|
18
|
+
'jwt-auth': () => new _JWTauthenticator.default(),
|
|
19
|
+
'oauth2': () => new _OAuthAuthenticator.default(),
|
|
20
|
+
'org-oauth': () => new _OrgOAuthAuthenticator.default(),
|
|
21
|
+
'cookie-auth': () => new _CookieAuthenticator.default()
|
|
22
|
+
};
|
|
23
|
+
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) || [];
|
|
24
|
+
const authenticationTypes = authentications.map(auth => String(auth).toLowerCase()).filter(Boolean);
|
|
25
|
+
for (const authenticationType of authenticationTypes) {
|
|
26
|
+
const authenticatorLoader = authenticationLoaders[authenticationType];
|
|
27
|
+
if (!authenticatorLoader) {
|
|
28
|
+
throw new _DataGeneratorError.AuthenticationError(`Unknown authentication type: "${authenticationType}". Supported types are: ${Object.keys(authenticationLoaders).join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
const authenticatorInstance = await authenticatorLoader();
|
|
31
|
+
apiPayload = await authenticatorInstance.constructPayload(apiPayload, actorInfo, page);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
return apiPayload;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = Authenticator;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _cookieAuthConfig = require("./cookieAuthConfig.js");
|
|
8
|
+
var _DataGeneratorError = require("../DataGeneratorError.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 {
|
|
15
|
+
async constructPayload(apiPayload, actorInfo, page = null) {
|
|
16
|
+
if (!page) {
|
|
17
|
+
throw new _DataGeneratorError.AuthenticationError('Cookie authentication failed: "page" is required but was not provided');
|
|
18
|
+
}
|
|
19
|
+
const {
|
|
20
|
+
csrfHeaderName
|
|
21
|
+
} = _cookieAuthConfig.COOKIE_AUTH_CONFIG;
|
|
22
|
+
const {
|
|
23
|
+
csrfToken,
|
|
24
|
+
cookieString
|
|
25
|
+
} = await _getCookies.call(CookieAuthenticator, page);
|
|
26
|
+
apiPayload.account.authHeaders = {
|
|
27
|
+
Cookie: cookieString,
|
|
28
|
+
[csrfHeaderName]: csrfToken
|
|
29
|
+
};
|
|
30
|
+
return apiPayload;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function _getCookies(page) {
|
|
34
|
+
const {
|
|
35
|
+
csrfCookieName,
|
|
36
|
+
csrfParamName
|
|
37
|
+
} = _cookieAuthConfig.COOKIE_AUTH_CONFIG;
|
|
38
|
+
const context = page.context();
|
|
39
|
+
const pageUrl = new URL(page.url());
|
|
40
|
+
const currentDomain = pageUrl.hostname;
|
|
41
|
+
const cookies = await context.cookies([page.url()]);
|
|
42
|
+
const csrfCookies = cookies.filter(cookie => cookie.domain.includes(currentDomain) && cookie.name === csrfCookieName);
|
|
43
|
+
return {
|
|
44
|
+
csrfToken: csrfCookies.map(c => `${csrfParamName}=${c.value}`).join('; '),
|
|
45
|
+
cookieString: cookies.map(c => `${c.name}=${c.value}`).join('; ')
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
var _default = exports.default = CookieAuthenticator;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _DataGeneratorError = require("../DataGeneratorError.js");
|
|
8
|
+
/**
|
|
9
|
+
* JWTauthenticator constructs payload by fetching a JWT access token
|
|
10
|
+
* from the authenticator app and attaching it to the account.jwt config.
|
|
11
|
+
*/
|
|
12
|
+
class JWTauthenticator {
|
|
13
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
14
|
+
const account = actorInfo['data-generator'].account;
|
|
15
|
+
if (!account) {
|
|
16
|
+
throw new _DataGeneratorError.AuthenticationError('JWT authentication failed: "account" is missing in actor configuration');
|
|
17
|
+
}
|
|
18
|
+
const jwtConf = account['jwt-oauth'];
|
|
19
|
+
if (!jwtConf) {
|
|
20
|
+
throw new _DataGeneratorError.AuthenticationError('JWT authentication failed: "jwt-oauth" is missing in actor configuration');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Build payload for authenticator service
|
|
24
|
+
const userToken = encodeURIComponent((actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo.email) || '');
|
|
25
|
+
const jwtAuthUrl = process.env.DG_JWT_AUTH_URL;
|
|
26
|
+
const responseType = jwtConf.response_type;
|
|
27
|
+
const clientId = jwtConf.client_id;
|
|
28
|
+
const tokenUrl = jwtConf.token_url;
|
|
29
|
+
const domainUrl = process.env.DG_DOMAIN_URL;
|
|
30
|
+
const authPayload = {
|
|
31
|
+
user_token: userToken,
|
|
32
|
+
jwt_secret: jwtConf.jwt_secret,
|
|
33
|
+
jwt_auth_url: jwtAuthUrl,
|
|
34
|
+
response_type: responseType,
|
|
35
|
+
client_id: clientId,
|
|
36
|
+
token_url: tokenUrl,
|
|
37
|
+
domain_url: domainUrl
|
|
38
|
+
};
|
|
39
|
+
const authenticatorApi = process.env.DG_JWT_AUTHENTICATOR_API;
|
|
40
|
+
const resp = await fetch(authenticatorApi, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(authPayload)
|
|
46
|
+
});
|
|
47
|
+
if (!resp.ok) {
|
|
48
|
+
const errorBody = await resp.text();
|
|
49
|
+
throw new Error(`JWT authenticator error! status: ${resp.status}, body: ${errorBody}`);
|
|
50
|
+
}
|
|
51
|
+
const json = await resp.json();
|
|
52
|
+
const accessToken = json === null || json === void 0 ? void 0 : json.access_token;
|
|
53
|
+
if (!accessToken) {
|
|
54
|
+
throw new Error('JWT authenticator did not return access_token');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Attach access token to authHeaders so downstream API calls can use it directly
|
|
58
|
+
apiPayload.account = apiPayload.account || {};
|
|
59
|
+
apiPayload.account.authHeaders = apiPayload.account.authHeaders || {};
|
|
60
|
+
apiPayload.account.authHeaders.Authorization = `Zoho-oauthtoken ${accessToken}`;
|
|
61
|
+
return apiPayload;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var _default = exports.default = JWTauthenticator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _DataGeneratorError = require("../DataGeneratorError.js");
|
|
8
|
+
/**
|
|
9
|
+
* OAuthAuthenticator prepares the payload for OAuth2-based authentication.
|
|
10
|
+
* It ensures oauth2 config is present in the account block so the
|
|
11
|
+
* data generator service can use it.
|
|
12
|
+
*/
|
|
13
|
+
class OAuthAuthenticator {
|
|
14
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
15
|
+
const account = actorInfo['data-generator'].account;
|
|
16
|
+
if (!account) {
|
|
17
|
+
throw new _DataGeneratorError.AuthenticationError('OAuth2 authentication failed: "account" is missing in actor configuration');
|
|
18
|
+
}
|
|
19
|
+
if (!account.oauth2) {
|
|
20
|
+
throw new _DataGeneratorError.AuthenticationError('OAuth2 authentication failed: "oauth2" is missing in actor configuration');
|
|
21
|
+
}
|
|
22
|
+
apiPayload.account.oauth2 = account.oauth2;
|
|
23
|
+
return apiPayload;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var _default = exports.default = OAuthAuthenticator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _DataGeneratorError = require("../DataGeneratorError.js");
|
|
8
|
+
/**
|
|
9
|
+
* OrgOAuthAuthenticator prepares the payload for org_oauth authentication.
|
|
10
|
+
* It ensures org_oauth config is present in the account block so the
|
|
11
|
+
* data generator service can use it.
|
|
12
|
+
*/
|
|
13
|
+
class OrgOAuthAuthenticator {
|
|
14
|
+
async constructPayload(apiPayload, actorInfo) {
|
|
15
|
+
const account = actorInfo['data-generator'].account;
|
|
16
|
+
if (!account) {
|
|
17
|
+
throw new _DataGeneratorError.AuthenticationError('OrgOAuth authentication failed: "account" is missing in actor configuration');
|
|
18
|
+
}
|
|
19
|
+
if (!account.org_oauth) {
|
|
20
|
+
throw new _DataGeneratorError.AuthenticationError('OrgOAuth authentication failed: "org_oauth" is missing in actor configuration');
|
|
21
|
+
}
|
|
22
|
+
apiPayload.account.org_oauth = account.org_oauth;
|
|
23
|
+
return apiPayload;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var _default = exports.default = OrgOAuthAuthenticator;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.COOKIE_AUTH_CONFIG = void 0;
|
|
7
|
+
const COOKIE_AUTH_CONFIG = exports.COOKIE_AUTH_CONFIG = {
|
|
8
|
+
csrfHeaderName: 'X-ZCSRF-TOKEN',
|
|
9
|
+
csrfCookieName: 'crmcsr',
|
|
10
|
+
csrfParamName: 'crmcsrfparam'
|
|
11
|
+
};
|
|
@@ -25,20 +25,20 @@ class ActorContext {
|
|
|
25
25
|
};
|
|
26
26
|
const additionalActors = (0, _additionalProfiles.additionProfiles)($tags);
|
|
27
27
|
await Promise.all(Object.entries(additionalActors).map(async ([role, actorInfo]) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
page,
|
|
28
|
+
const additionalContext = await browser.newContext();
|
|
29
|
+
const additionalPage = await additionalContext.newPage();
|
|
30
|
+
const ctxTestDetails = {
|
|
31
|
+
page: additionalPage,
|
|
32
32
|
$tags,
|
|
33
|
-
context,
|
|
33
|
+
context: additionalContext,
|
|
34
34
|
...actorInfo
|
|
35
35
|
};
|
|
36
|
-
await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(
|
|
36
|
+
await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(additionalContext, testInfo, ctxTestDetails, actorInfo);
|
|
37
37
|
this.actorsObj[role] = {
|
|
38
38
|
role,
|
|
39
39
|
browser,
|
|
40
|
-
context,
|
|
41
|
-
page,
|
|
40
|
+
context: additionalContext,
|
|
41
|
+
page: additionalPage,
|
|
42
42
|
executionContext: {
|
|
43
43
|
actorInfo
|
|
44
44
|
}
|
|
@@ -69,7 +69,10 @@ var _default = exports.default = {
|
|
|
69
69
|
}, use, testInfo) => {
|
|
70
70
|
const ctxObject = new ActorContext();
|
|
71
71
|
await ctxObject.setup(browser, $tags, testInfo, context, page, executionContext);
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
try {
|
|
73
|
+
await use(ctxObject);
|
|
74
|
+
} finally {
|
|
75
|
+
await ctxObject.cleanup();
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
};
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
7
|
exports.default = void 0;
|
|
8
|
+
var _path = _interopRequireDefault(require("path"));
|
|
7
9
|
var _readConfigFile = require("../readConfigFile");
|
|
10
|
+
var _logCollector = require("../helpers/logCollector");
|
|
8
11
|
const {
|
|
9
|
-
testSetup
|
|
12
|
+
testSetup,
|
|
13
|
+
uatDirectory,
|
|
14
|
+
browserLogs = {}
|
|
10
15
|
} = (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
|
+
const MAX_FILENAME_LENGTH = 120;
|
|
21
|
+
function sanitizeForFilename(name) {
|
|
22
|
+
if (!name) return 'test';
|
|
23
|
+
const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, MAX_FILENAME_LENGTH);
|
|
24
|
+
return cleaned || 'test';
|
|
25
|
+
}
|
|
11
26
|
async function performDefaultContextSteps({
|
|
12
27
|
context
|
|
13
28
|
}) {
|
|
@@ -20,13 +35,37 @@ async function performDefaultContextSteps({
|
|
|
20
35
|
var _default = exports.default = {
|
|
21
36
|
context: async ({
|
|
22
37
|
context
|
|
23
|
-
}, use) => {
|
|
38
|
+
}, use, testInfo) => {
|
|
24
39
|
await context.addInitScript(() =>
|
|
25
40
|
// eslint-disable-next-line no-undef
|
|
26
41
|
window.localStorage.setItem('isDnBannerHide', true));
|
|
27
42
|
await performDefaultContextSteps({
|
|
28
43
|
context
|
|
29
44
|
});
|
|
30
|
-
|
|
45
|
+
const collector = new _logCollector.LogCollector(browserLogs.limits);
|
|
46
|
+
collector.attachToContext(context);
|
|
47
|
+
testInfo.runtimeLogs = collector;
|
|
48
|
+
try {
|
|
49
|
+
await use(context);
|
|
50
|
+
} finally {
|
|
51
|
+
collector.detach();
|
|
52
|
+
const failureOnly = browserLogs.mode === 'failure-only';
|
|
53
|
+
const isFailure = testInfo.status && testInfo.status !== testInfo.expectedStatus;
|
|
54
|
+
const shouldFlush = !failureOnly || isFailure;
|
|
55
|
+
const summary = collector.toSummary();
|
|
56
|
+
let filePath = null;
|
|
57
|
+
if (shouldFlush) {
|
|
58
|
+
const base = `w${testInfo.workerIndex}-${sanitizeForFilename(testInfo.title)}-${testInfo.retry}`;
|
|
59
|
+
filePath = collector.flushToFile(LOGS_DIR, base);
|
|
60
|
+
}
|
|
61
|
+
await testInfo.attach('runtime-log', {
|
|
62
|
+
contentType: 'application/json',
|
|
63
|
+
body: Buffer.from(JSON.stringify({
|
|
64
|
+
schemaVersion: 1,
|
|
65
|
+
summary,
|
|
66
|
+
file: filePath
|
|
67
|
+
}))
|
|
68
|
+
});
|
|
69
|
+
}
|
|
31
70
|
}
|
|
32
71
|
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.LogCollector = void 0;
|
|
8
|
+
var _path = _interopRequireDefault(require("path"));
|
|
9
|
+
var _fileUtils = require("../../../utils/fileUtils");
|
|
10
|
+
// Per-context Playwright runtime event collector — network failures,
|
|
11
|
+
// console errors, page errors and crashes. Buffered in-memory and flushed
|
|
12
|
+
// once per test via writeFileContents.
|
|
13
|
+
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
maxEvents: 2000,
|
|
16
|
+
maxBodyBytes: 0,
|
|
17
|
+
captureHeaders: false,
|
|
18
|
+
ignoreMessagePatterns: [/n\.enableDarkTheme is not a function/],
|
|
19
|
+
ignoreUrlPatterns: [/\.(png|jpe?g|gif|svg|webp|woff2?|ttf|css|map)(\?|$)/i, /google-analytics\.com|googletagmanager\.com|sentry\.io/i]
|
|
20
|
+
};
|
|
21
|
+
class LogCollector {
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
this.config = {
|
|
24
|
+
...DEFAULT_CONFIG,
|
|
25
|
+
...config
|
|
26
|
+
};
|
|
27
|
+
this.events = [];
|
|
28
|
+
this.truncated = false;
|
|
29
|
+
this.counts = {
|
|
30
|
+
console: 0,
|
|
31
|
+
consoleError: 0,
|
|
32
|
+
pageError: 0,
|
|
33
|
+
request: 0,
|
|
34
|
+
response: 0,
|
|
35
|
+
failed: 0,
|
|
36
|
+
crash: 0
|
|
37
|
+
};
|
|
38
|
+
this._bound = [];
|
|
39
|
+
}
|
|
40
|
+
_push(evt) {
|
|
41
|
+
if (this.events.length >= this.config.maxEvents) {
|
|
42
|
+
this.truncated = true;
|
|
43
|
+
this.events.shift();
|
|
44
|
+
}
|
|
45
|
+
this.events.push({
|
|
46
|
+
ts: Date.now(),
|
|
47
|
+
...evt
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
_shouldIgnore(url) {
|
|
51
|
+
return this.config.ignoreUrlPatterns.some(re => re.test(url));
|
|
52
|
+
}
|
|
53
|
+
_shouldIgnoreMessage(message) {
|
|
54
|
+
if (!message) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return this.config.ignoreMessagePatterns.some(re => re.test(message));
|
|
58
|
+
}
|
|
59
|
+
_bind(target, event, handler) {
|
|
60
|
+
target.on(event, handler);
|
|
61
|
+
this._bound.push([target, event, handler]);
|
|
62
|
+
}
|
|
63
|
+
attachToContext(context) {
|
|
64
|
+
context.pages().forEach(p => this._attachPage(p));
|
|
65
|
+
this._bind(context, 'page', p => this._attachPage(p));
|
|
66
|
+
this._bind(context, 'request', req => {
|
|
67
|
+
if (this._shouldIgnore(req.url())) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.counts.request++;
|
|
71
|
+
});
|
|
72
|
+
this._bind(context, 'response', res => {
|
|
73
|
+
const url = res.url();
|
|
74
|
+
if (this._shouldIgnore(url)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.counts.response++;
|
|
78
|
+
if (res.status() >= 400) {
|
|
79
|
+
this._push({
|
|
80
|
+
kind: 'response',
|
|
81
|
+
status: res.status(),
|
|
82
|
+
method: res.request().method(),
|
|
83
|
+
url,
|
|
84
|
+
fromCache: res.fromServiceWorker()
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this._bind(context, 'requestfailed', req => {
|
|
89
|
+
var _req$failure;
|
|
90
|
+
this.counts.failed++;
|
|
91
|
+
this._push({
|
|
92
|
+
kind: 'requestfailed',
|
|
93
|
+
method: req.method(),
|
|
94
|
+
url: req.url(),
|
|
95
|
+
failure: (_req$failure = req.failure()) === null || _req$failure === void 0 ? void 0 : _req$failure.errorText,
|
|
96
|
+
resourceType: req.resourceType()
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
_attachPage(page) {
|
|
101
|
+
this._bind(page, 'console', msg => {
|
|
102
|
+
const type = msg.type();
|
|
103
|
+
this.counts.console++;
|
|
104
|
+
if (type !== 'error') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const text = msg.text();
|
|
108
|
+
if (this._shouldIgnoreMessage(text)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.counts.consoleError++;
|
|
112
|
+
this._push({
|
|
113
|
+
kind: 'console',
|
|
114
|
+
level: type,
|
|
115
|
+
text,
|
|
116
|
+
location: msg.location()
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
this._bind(page, 'pageerror', err => {
|
|
120
|
+
if (this._shouldIgnoreMessage(err.message)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.counts.pageError++;
|
|
124
|
+
this._push({
|
|
125
|
+
kind: 'pageerror',
|
|
126
|
+
name: err.name,
|
|
127
|
+
message: err.message,
|
|
128
|
+
stack: err.stack
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
this._bind(page, 'crash', () => {
|
|
132
|
+
this.counts.crash++;
|
|
133
|
+
this._push({
|
|
134
|
+
kind: 'crash',
|
|
135
|
+
url: page.url()
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
detach() {
|
|
140
|
+
for (const [target, event, handler] of this._bound) {
|
|
141
|
+
try {
|
|
142
|
+
target.removeListener(event, handler);
|
|
143
|
+
} catch {
|
|
144
|
+
// context may already be closed
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this._bound.length = 0;
|
|
148
|
+
}
|
|
149
|
+
toSummary() {
|
|
150
|
+
return {
|
|
151
|
+
...this.counts,
|
|
152
|
+
total: this.events.length,
|
|
153
|
+
truncated: this.truncated
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
flushToFile(dir, baseName) {
|
|
157
|
+
if (this.events.length === 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const file = _path.default.join(dir, `${baseName}.jsonl`);
|
|
161
|
+
const body = this.events.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
162
|
+
(0, _fileUtils.writeFileContents)(file, body);
|
|
163
|
+
this.events.length = 0;
|
|
164
|
+
return file;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.LogCollector = LogCollector;
|