@zohodesk/testinglibrary 0.0.74-n20-experimental → 0.0.82-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
|
@@ -37,6 +37,7 @@ function getDefaultConfig() {
|
|
|
37
37
|
forbidOnly: false,
|
|
38
38
|
retries: 0,
|
|
39
39
|
trace: false,
|
|
40
|
+
smokeTestTrace: 'on',
|
|
40
41
|
video: false,
|
|
41
42
|
isAuthMode: false,
|
|
42
43
|
openReportOn: 'never',
|
|
@@ -55,7 +56,11 @@ function getDefaultConfig() {
|
|
|
55
56
|
featureFilesFolder: 'feature-files',
|
|
56
57
|
stepDefinitionsFolder: 'steps',
|
|
57
58
|
testSetup: {},
|
|
58
|
-
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
|
|
59
|
+
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
|
|
60
|
+
showCaseTimings: false,
|
|
61
|
+
browserLogs: {
|
|
62
|
+
mode: 'failure-only'
|
|
63
|
+
}
|
|
59
64
|
};
|
|
60
65
|
}
|
|
61
66
|
function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
@@ -96,6 +101,7 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
|
96
101
|
* @property {string} uatDirectory - Directory in which uat configuration is places.
|
|
97
102
|
* @property {string} headless - Headless Browsers mode.
|
|
98
103
|
* @property {number} trace - trace for test cases.
|
|
104
|
+
* @property {string} smokeTestTrace - trace mode for the smokeTest project (e.g. 'on', 'off', 'retain-on-failure'). Default is 'on'.
|
|
99
105
|
* @property {boolean} video - video for test cases,
|
|
100
106
|
* @property {boolean} debug - debug mode
|
|
101
107
|
* @property {boolean} isAuthMode - Auth Mode. config whether authentication step needed before running test cases
|
|
@@ -113,6 +119,8 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
|
113
119
|
* @property {string} testIdAttribute: Change the default data-testid attribute. configure what attribute to search while calling getByTestId
|
|
114
120
|
* @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
|
|
115
121
|
* @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
|
|
122
|
+
* @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.
|
|
123
|
+
* @property {Object} browserLogs: Runtime browser log capture. { mode: 'failure-only' | 'always', limits?: { maxEvents, captureHeaders, ignoreUrlPatterns, ignoreMessagePatterns } }. ignoreMessagePatterns is a list of RegExp matched against console-error text and pageerror messages; matching events are skipped. Default: { mode: 'failure-only' }.
|
|
116
124
|
*/
|
|
117
125
|
|
|
118
126
|
/**
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
bddMode,
|
|
22
22
|
authFilePath,
|
|
23
23
|
trace,
|
|
24
|
+
smokeTestTrace,
|
|
24
25
|
video,
|
|
25
26
|
testIdAttribute,
|
|
26
27
|
viewport,
|
|
@@ -44,7 +45,8 @@ function smokeTestConfig() {
|
|
|
44
45
|
uatPath: _path.default.join(process.cwd(), _configConstants.default.TEST_SLICE_FOLDER, stage, 'smokeTest')
|
|
45
46
|
});
|
|
46
47
|
const commonConfig = {
|
|
47
|
-
storageState: isAuthMode ? (0, _readConfigFile.getAuthFilePath)(_path.default.resolve(process.cwd(), authFilePath)) : {}
|
|
48
|
+
storageState: isAuthMode ? (0, _readConfigFile.getAuthFilePath)(_path.default.resolve(process.cwd(), authFilePath)) : {},
|
|
49
|
+
trace: smokeTestTrace
|
|
48
50
|
};
|
|
49
51
|
smokeTestProject.setTestDir(smokeTestDir);
|
|
50
52
|
smokeTestProject.setRetries(0);
|
|
@@ -24,6 +24,22 @@ function getProjectName(test) {
|
|
|
24
24
|
}
|
|
25
25
|
return '';
|
|
26
26
|
}
|
|
27
|
+
function formatTime(date) {
|
|
28
|
+
return date.toLocaleTimeString('en-US', {
|
|
29
|
+
hour: 'numeric',
|
|
30
|
+
minute: '2-digit',
|
|
31
|
+
second: '2-digit',
|
|
32
|
+
hour12: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function formatDuration(ms) {
|
|
36
|
+
if (ms < 1000) return `${ms}ms`;
|
|
37
|
+
const totalSeconds = ms / 1000;
|
|
38
|
+
if (totalSeconds < 60) return `${totalSeconds.toFixed(2)}s`;
|
|
39
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
40
|
+
const seconds = (totalSeconds - minutes * 60).toFixed(2);
|
|
41
|
+
return `${minutes}m ${seconds}s`;
|
|
42
|
+
}
|
|
27
43
|
class JSONSummaryReporter {
|
|
28
44
|
constructor() {
|
|
29
45
|
this.durationInMS = -1;
|
|
@@ -38,12 +54,23 @@ class JSONSummaryReporter {
|
|
|
38
54
|
this.failedSteps = [];
|
|
39
55
|
this.status = 'unknown';
|
|
40
56
|
this.startedAt = 0;
|
|
41
|
-
|
|
57
|
+
const config = (0, _readConfigFile.generateConfigFromFile)();
|
|
58
|
+
this._open = config.openReportOn;
|
|
59
|
+
this._showCaseTimings = config.showCaseTimings !== false;
|
|
60
|
+
this._caseTimings = [];
|
|
42
61
|
this.failedProject = null;
|
|
43
62
|
}
|
|
44
63
|
onBegin() {
|
|
45
64
|
this.startedAt = Date.now();
|
|
46
65
|
}
|
|
66
|
+
onTestBegin(test) {
|
|
67
|
+
if (!this._showCaseTimings) return;
|
|
68
|
+
const {
|
|
69
|
+
fullTitle
|
|
70
|
+
} = this.getTitle(test);
|
|
71
|
+
const startedAtLabel = formatTime(new Date());
|
|
72
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, `▶ ${fullTitle} — started at ${startedAtLabel}`);
|
|
73
|
+
}
|
|
47
74
|
getTitle(test) {
|
|
48
75
|
const title = [];
|
|
49
76
|
const fileName = [];
|
|
@@ -89,6 +116,55 @@ class JSONSummaryReporter {
|
|
|
89
116
|
this[status].push(fileName);
|
|
90
117
|
}
|
|
91
118
|
this[status].push(fileName);
|
|
119
|
+
if (this._showCaseTimings && result.startTime) {
|
|
120
|
+
const startDate = new Date(result.startTime);
|
|
121
|
+
const endDate = new Date(startDate.getTime() + (result.duration || 0));
|
|
122
|
+
const startLabel = formatTime(startDate);
|
|
123
|
+
const endLabel = formatTime(endDate);
|
|
124
|
+
const durationLabel = formatDuration(result.duration || 0);
|
|
125
|
+
const statusGlyph = result.status === 'passed' ? '✓' : result.status === 'skipped' ? '○' : '✗';
|
|
126
|
+
const logType = result.status === 'passed' ? _logger.Logger.SUCCESS_TYPE : result.status === 'skipped' ? _logger.Logger.INFO_TYPE : _logger.Logger.FAILURE_TYPE;
|
|
127
|
+
_logger.Logger.log(logType, `${statusGlyph} ${fullTitle} — ended at ${endLabel} (started ${startLabel}, took ${durationLabel})`);
|
|
128
|
+
const isFailure = result.status !== 'passed' && result.status !== 'skipped';
|
|
129
|
+
if (isFailure) {
|
|
130
|
+
var _result$error;
|
|
131
|
+
const runtimeAttachment = (result.attachments || []).find(a => a.name === 'runtime-log' && a.body);
|
|
132
|
+
let runtime = null;
|
|
133
|
+
if (runtimeAttachment) {
|
|
134
|
+
try {
|
|
135
|
+
runtime = JSON.parse(runtimeAttachment.body.toString('utf8'));
|
|
136
|
+
} catch {
|
|
137
|
+
// malformed attachment; skip
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
this._caseTimings.push({
|
|
141
|
+
title: fullTitle,
|
|
142
|
+
fileName,
|
|
143
|
+
status: result.status,
|
|
144
|
+
retry: result.retry,
|
|
145
|
+
startTime: startDate.toISOString(),
|
|
146
|
+
endTime: endDate.toISOString(),
|
|
147
|
+
startTimeFormatted: startLabel,
|
|
148
|
+
endTimeFormatted: endLabel,
|
|
149
|
+
duration: result.duration || 0,
|
|
150
|
+
durationFormatted: durationLabel,
|
|
151
|
+
errorMessage: (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.message,
|
|
152
|
+
failedSteps: (result.steps || []).filter(step => step.error).map(step => {
|
|
153
|
+
var _step$error;
|
|
154
|
+
return {
|
|
155
|
+
title: step.title,
|
|
156
|
+
error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
|
|
157
|
+
};
|
|
158
|
+
}),
|
|
159
|
+
...(runtime ? {
|
|
160
|
+
runtime: {
|
|
161
|
+
summary: runtime.summary,
|
|
162
|
+
logFile: runtime.file
|
|
163
|
+
}
|
|
164
|
+
} : {})
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
92
168
|
const isFailure = result.status !== 'passed' && result.status !== 'skipped';
|
|
93
169
|
if (isFailure && !this.failedProject) {
|
|
94
170
|
const projectName = getProjectName(test);
|
|
@@ -145,6 +221,16 @@ class JSONSummaryReporter {
|
|
|
145
221
|
reportPath
|
|
146
222
|
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
147
223
|
(0, _fileUtils.writeFileContents)(_path.default.join(reportPath, './', (0, _configFileNameProvider.getReportFileName)()), JSON.stringify(this, null, ' '));
|
|
224
|
+
if (this._showCaseTimings && this._caseTimings.length > 0) {
|
|
225
|
+
const timingsPayload = {
|
|
226
|
+
suiteStartedAt: new Date(this.startedAt).toISOString(),
|
|
227
|
+
suiteEndedAt: new Date(this.startedAt + this.durationInMS).toISOString(),
|
|
228
|
+
suiteDurationMs: this.durationInMS,
|
|
229
|
+
failedCount: this._caseTimings.length,
|
|
230
|
+
cases: this._caseTimings
|
|
231
|
+
};
|
|
232
|
+
(0, _fileUtils.writeFileContents)(_path.default.join(reportPath, 'case-timings.json'), JSON.stringify(timingsPayload, null, ' '));
|
|
233
|
+
}
|
|
148
234
|
}
|
|
149
235
|
onExit() {
|
|
150
236
|
// Update .last-run.json with aborted tests due to timing out or interruption
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* @property {string} uatDirectory - Directory in which uat configuration is places.
|
|
22
22
|
* @property {string} headless - Headless Browsers mode.
|
|
23
23
|
* @property {number} trace - trace for test cases.
|
|
24
|
+
* @property {string} smokeTestTrace - trace mode for the smokeTest project (e.g. 'on', 'off', 'retain-on-failure'). Default is 'on'.
|
|
24
25
|
* @property {boolean} video - video for test cases,
|
|
25
26
|
* @property {boolean} debug - debug mode
|
|
26
27
|
* @property {string} mode: mode in which the test cases needs to run
|
|
@@ -9,6 +9,7 @@ const testSetup = require('../../fixtures/testSetup');
|
|
|
9
9
|
* @typedef {Object} UserConfig
|
|
10
10
|
* @property {string} headless - Headless Browsers mode.
|
|
11
11
|
* @property {number} trace - trace for test cases.
|
|
12
|
+
* @property {string} smokeTestTrace - trace mode for the smokeTest project (e.g. 'on', 'off', 'retain-on-failure'). Default is 'on'.
|
|
12
13
|
* @property {boolean} video - video for test cases,
|
|
13
14
|
* @property {boolean} debug - debug mode
|
|
14
15
|
* @property {string} mode: mode in which the test cases needs to run
|
|
@@ -37,6 +38,7 @@ module.exports = {
|
|
|
37
38
|
isAuthMode: true,
|
|
38
39
|
authFilePath: 'uat/playwright/.auth/user.json',
|
|
39
40
|
trace: true,
|
|
41
|
+
smokeTestTrace: 'on',
|
|
40
42
|
video: true,
|
|
41
43
|
bddMode: true,
|
|
42
44
|
featureFilesFolder: 'feature-files',
|
|
@@ -0,0 +1,88 @@
|
|
|
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 throw an error when an unknown authentication type is present', 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
|
+
await expect(authenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('Unknown authentication type: "apikey"');
|
|
21
|
+
});
|
|
22
|
+
it('should route oauth2 authentication and merge oauth2 config into payload', async () => {
|
|
23
|
+
const authenticator = new _Authenticator.default();
|
|
24
|
+
const apiPayload = {
|
|
25
|
+
account: {}
|
|
26
|
+
};
|
|
27
|
+
const actorInfo = {
|
|
28
|
+
'data-generator': {
|
|
29
|
+
account: {
|
|
30
|
+
authentications: ['oauth2'],
|
|
31
|
+
oauth2: {
|
|
32
|
+
client_id: 'client_1',
|
|
33
|
+
client_secret: 'secret_1'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
39
|
+
expect(result.account.oauth2).toEqual({
|
|
40
|
+
client_id: 'client_1',
|
|
41
|
+
client_secret: 'secret_1'
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('should treat authentication values case-insensitively and deduplicate them', async () => {
|
|
45
|
+
const authenticator = new _Authenticator.default();
|
|
46
|
+
const apiPayload = {
|
|
47
|
+
account: {}
|
|
48
|
+
};
|
|
49
|
+
const actorInfo = {
|
|
50
|
+
'data-generator': {
|
|
51
|
+
account: {
|
|
52
|
+
authentications: ['OAUTH2', 'oauth2', 'OAuth2'],
|
|
53
|
+
oauth2: {
|
|
54
|
+
scope: 'read'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
60
|
+
expect(result.account.oauth2).toEqual({
|
|
61
|
+
scope: 'read'
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('should apply only the first supported authentication type', async () => {
|
|
65
|
+
const authenticator = new _Authenticator.default();
|
|
66
|
+
const apiPayload = {
|
|
67
|
+
account: {}
|
|
68
|
+
};
|
|
69
|
+
const actorInfo = {
|
|
70
|
+
'data-generator': {
|
|
71
|
+
account: {
|
|
72
|
+
authentications: ['oauth2', 'org-oauth'],
|
|
73
|
+
oauth2: {
|
|
74
|
+
client_id: 'oauth-client'
|
|
75
|
+
},
|
|
76
|
+
org_oauth: {
|
|
77
|
+
org_id: 'org-101'
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const result = await authenticator.constructPayload(apiPayload, actorInfo);
|
|
83
|
+
expect(result.account.oauth2).toEqual({
|
|
84
|
+
client_id: 'oauth-client'
|
|
85
|
+
});
|
|
86
|
+
expect(result.account.org_oauth).toBeUndefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _JWTauthenticator = _interopRequireDefault(require("../../../../../../src/core/dataGenerator/authenticator/JWTauthenticator"));
|
|
5
|
+
describe('JWTauthenticator', () => {
|
|
6
|
+
let fetchMock;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
fetchMock = jest.fn();
|
|
9
|
+
global.fetch = fetchMock;
|
|
10
|
+
process.env.DG_JWT_AUTH_URL = 'https://jwt-auth.example.com';
|
|
11
|
+
process.env.DG_DOMAIN_URL = 'https://domain.example.com';
|
|
12
|
+
process.env.DG_JWT_AUTHENTICATOR_API = 'https://authenticator.example.com/jwtToken';
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
delete global.fetch;
|
|
16
|
+
delete process.env.DG_JWT_AUTH_URL;
|
|
17
|
+
delete process.env.DG_DOMAIN_URL;
|
|
18
|
+
delete process.env.DG_JWT_AUTHENTICATOR_API;
|
|
19
|
+
});
|
|
20
|
+
it('should throw when jwt-oauth config is missing', async () => {
|
|
21
|
+
const jwtAuthenticator = new _JWTauthenticator.default();
|
|
22
|
+
const apiPayload = {
|
|
23
|
+
account: {}
|
|
24
|
+
};
|
|
25
|
+
const actorInfo = {
|
|
26
|
+
email: 'user@example.com',
|
|
27
|
+
'data-generator': {
|
|
28
|
+
account: {}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
await expect(jwtAuthenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('JWT authentication failed: "jwt-oauth" is missing in actor configuration');
|
|
32
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
it('should fetch jwt access token and set it in authHeaders', async () => {
|
|
35
|
+
fetchMock.mockResolvedValue({
|
|
36
|
+
ok: true,
|
|
37
|
+
json: async () => ({
|
|
38
|
+
access_token: 'access-token-123'
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
const jwtAuthenticator = new _JWTauthenticator.default();
|
|
42
|
+
const apiPayload = {
|
|
43
|
+
account: {}
|
|
44
|
+
};
|
|
45
|
+
const actorInfo = {
|
|
46
|
+
email: 'user.one@example.com',
|
|
47
|
+
'data-generator': {
|
|
48
|
+
account: {
|
|
49
|
+
'jwt-oauth': {
|
|
50
|
+
jwt_secret: 'jwt-secret',
|
|
51
|
+
response_type: 'token',
|
|
52
|
+
client_id: '12345.client-id',
|
|
53
|
+
token_url: 'https://token.example.com/12345/oauth'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const result = await jwtAuthenticator.constructPayload(apiPayload, actorInfo);
|
|
59
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(fetchMock).toHaveBeenCalledWith('https://authenticator.example.com/jwtToken', expect.objectContaining({
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json'
|
|
64
|
+
}
|
|
65
|
+
}));
|
|
66
|
+
const requestBody = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
67
|
+
expect(requestBody).toEqual(expect.objectContaining({
|
|
68
|
+
user_token: encodeURIComponent('user.one@example.com'),
|
|
69
|
+
jwt_secret: 'jwt-secret',
|
|
70
|
+
jwt_auth_url: 'https://jwt-auth.example.com',
|
|
71
|
+
response_type: 'token',
|
|
72
|
+
client_id: '12345.client-id',
|
|
73
|
+
token_url: 'https://token.example.com/12345/oauth',
|
|
74
|
+
domain_url: 'https://domain.example.com'
|
|
75
|
+
}));
|
|
76
|
+
expect(result.account.authHeaders.Authorization).toBe('Zoho-oauthtoken access-token-123');
|
|
77
|
+
});
|
|
78
|
+
it('should throw a descriptive error when authenticator API returns non-ok response', async () => {
|
|
79
|
+
fetchMock.mockResolvedValue({
|
|
80
|
+
ok: false,
|
|
81
|
+
status: 500,
|
|
82
|
+
text: async () => 'internal error'
|
|
83
|
+
});
|
|
84
|
+
const jwtAuthenticator = new _JWTauthenticator.default();
|
|
85
|
+
const apiPayload = {
|
|
86
|
+
account: {}
|
|
87
|
+
};
|
|
88
|
+
const actorInfo = {
|
|
89
|
+
email: 'user@example.com',
|
|
90
|
+
'data-generator': {
|
|
91
|
+
account: {
|
|
92
|
+
'jwt-oauth': {
|
|
93
|
+
jwt_secret: 'jwt-secret',
|
|
94
|
+
response_type: 'token',
|
|
95
|
+
client_id: '12345.client-id',
|
|
96
|
+
token_url: 'https://token.example.com/12345/oauth'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
await expect(jwtAuthenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('JWT authenticator error! status: 500, body: internal error');
|
|
102
|
+
});
|
|
103
|
+
it('should throw when authenticator response has no access_token', async () => {
|
|
104
|
+
fetchMock.mockResolvedValue({
|
|
105
|
+
ok: true,
|
|
106
|
+
json: async () => ({
|
|
107
|
+
token_type: 'Bearer'
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
const jwtAuthenticator = new _JWTauthenticator.default();
|
|
111
|
+
const apiPayload = {
|
|
112
|
+
account: {}
|
|
113
|
+
};
|
|
114
|
+
const actorInfo = {
|
|
115
|
+
email: 'user@example.com',
|
|
116
|
+
'data-generator': {
|
|
117
|
+
account: {
|
|
118
|
+
'jwt-oauth': {
|
|
119
|
+
jwt_secret: 'jwt-secret',
|
|
120
|
+
response_type: 'token',
|
|
121
|
+
client_id: '12345.client-id',
|
|
122
|
+
token_url: 'https://token.example.com/12345/oauth'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
await expect(jwtAuthenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('JWT authenticator did not return access_token');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _OAuthAuthenticator = _interopRequireDefault(require("../../../../../../src/core/dataGenerator/authenticator/OAuthAuthenticator"));
|
|
5
|
+
describe('OAuthAuthenticator', () => {
|
|
6
|
+
it('should assign oauth2 from actor info into payload account', async () => {
|
|
7
|
+
const oauthAuthenticator = new _OAuthAuthenticator.default();
|
|
8
|
+
const apiPayload = {
|
|
9
|
+
account: {}
|
|
10
|
+
};
|
|
11
|
+
const actorInfo = {
|
|
12
|
+
'data-generator': {
|
|
13
|
+
account: {
|
|
14
|
+
oauth2: {
|
|
15
|
+
client_id: 'new-client',
|
|
16
|
+
scope: 'tickets.read'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const result = await oauthAuthenticator.constructPayload(apiPayload, actorInfo);
|
|
22
|
+
expect(result.account.oauth2).toEqual({
|
|
23
|
+
client_id: 'new-client',
|
|
24
|
+
scope: 'tickets.read'
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it('should throw when oauth2 is absent in actor configuration', async () => {
|
|
28
|
+
const oauthAuthenticator = new _OAuthAuthenticator.default();
|
|
29
|
+
const apiPayload = {
|
|
30
|
+
account: {}
|
|
31
|
+
};
|
|
32
|
+
const actorInfo = {
|
|
33
|
+
'data-generator': {
|
|
34
|
+
account: {}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
await expect(oauthAuthenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('OAuth2 authentication failed: "oauth2" is missing in actor configuration');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _OrgOAuthAuthenticator = _interopRequireDefault(require("../../../../../../src/core/dataGenerator/authenticator/OrgOAuthAuthenticator"));
|
|
5
|
+
describe('OrgOAuthAuthenticator', () => {
|
|
6
|
+
it('should assign org_oauth from actor info into payload account', async () => {
|
|
7
|
+
const orgOAuthAuthenticator = new _OrgOAuthAuthenticator.default();
|
|
8
|
+
const apiPayload = {
|
|
9
|
+
account: {}
|
|
10
|
+
};
|
|
11
|
+
const actorInfo = {
|
|
12
|
+
'data-generator': {
|
|
13
|
+
account: {
|
|
14
|
+
org_oauth: {
|
|
15
|
+
org_id: 'org-1001',
|
|
16
|
+
region: 'us'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const result = await orgOAuthAuthenticator.constructPayload(apiPayload, actorInfo);
|
|
22
|
+
expect(result.account.org_oauth).toEqual({
|
|
23
|
+
org_id: 'org-1001',
|
|
24
|
+
region: 'us'
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it('should throw when org_oauth is absent in actor configuration', async () => {
|
|
28
|
+
const orgOAuthAuthenticator = new _OrgOAuthAuthenticator.default();
|
|
29
|
+
const apiPayload = {
|
|
30
|
+
account: {}
|
|
31
|
+
};
|
|
32
|
+
const actorInfo = {
|
|
33
|
+
'data-generator': {
|
|
34
|
+
account: {}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
await expect(orgOAuthAuthenticator.constructPayload(apiPayload, actorInfo)).rejects.toThrow('OrgOAuth authentication failed: "org_oauth" is missing in actor configuration');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -13,7 +13,10 @@ jest.mock('../../../../core/playwright/helpers/parseUserArgs', () => ({
|
|
|
13
13
|
}));
|
|
14
14
|
jest.mock('../../../../core/playwright/readConfigFile');
|
|
15
15
|
jest.mock('../../../../core/playwright/tagProcessor');
|
|
16
|
-
jest.mock('../../../../core/playwright/test-runner')
|
|
16
|
+
jest.mock('../../../../core/playwright/test-runner', () => ({
|
|
17
|
+
__esModule: true,
|
|
18
|
+
runPreprocessing: jest.fn()
|
|
19
|
+
}));
|
|
17
20
|
jest.mock('../../../../utils/logger', () => ({
|
|
18
21
|
__esModule: true,
|
|
19
22
|
Logger: {
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
4
|
var _RunnerHelper = _interopRequireDefault(require("../../../../../core/playwright/runner/RunnerHelper"));
|
|
5
5
|
var _SpawnRunner = _interopRequireDefault(require("../../../../../core/playwright/runner/SpawnRunner"));
|
|
6
|
+
jest.mock('../../../../../core/playwright/setup/config-utils', () => ({
|
|
7
|
+
getBrowsersList: jest.fn().mockReturnValue([])
|
|
8
|
+
}));
|
|
6
9
|
describe('RunnerHelper', () => {
|
|
7
10
|
describe('createRunner', () => {
|
|
8
11
|
it('should throw error on invalid runner type', () => {
|
|
@@ -6,6 +6,9 @@ var _Runner = _interopRequireDefault(require("../../../../../core/playwright/run
|
|
|
6
6
|
var _Configuration = _interopRequireDefault(require("../../../../../core/playwright/configuration/Configuration"));
|
|
7
7
|
jest.mock('child_process');
|
|
8
8
|
jest.mock('../../../../../utils/logger');
|
|
9
|
+
jest.mock('../../../../../core/playwright/setup/config-utils', () => ({
|
|
10
|
+
getBrowsersList: jest.fn().mockReturnValue([])
|
|
11
|
+
}));
|
|
9
12
|
describe('SpawnRunner', () => {
|
|
10
13
|
let spawnRunner;
|
|
11
14
|
const runnerObj = new _Runner.default();
|