@actionlogementservices/wdio-test-context 1.0.0
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/.editorconfig +20 -0
- package/.jsdoc.json +35 -0
- package/.prettierrc +11 -0
- package/README.md +116 -0
- package/doc/images/badges/modifier-abstract.png +0 -0
- package/doc/images/badges/modifier-abstract.svg +10 -0
- package/doc/images/badges/modifier-default.png +0 -0
- package/doc/images/badges/modifier-default.svg +10 -0
- package/doc/images/badges/modifier-private.png +0 -0
- package/doc/images/badges/modifier-private.svg +10 -0
- package/doc/images/badges/modifier-protected.png +0 -0
- package/doc/images/badges/modifier-protected.svg +10 -0
- package/doc/images/badges/modifier-public.png +0 -0
- package/doc/images/badges/modifier-public.svg +10 -0
- package/doc/images/badges/modifier-readonly.png +0 -0
- package/doc/images/badges/modifier-readonly.svg +10 -0
- package/doc/images/badges/modifier-static.png +0 -0
- package/doc/images/badges/modifier-static.svg +10 -0
- package/doc/src-page-objects_guerrillamail.page.md +69 -0
- package/doc/src-page-objects_maildrop.page.md +57 -0
- package/doc/src-page-objects_page.md +484 -0
- package/doc/src-page-objects_yopmail.page.md +57 -0
- package/doc/src-wdio_ado-reporter.md +93 -0
- package/doc/src-wdio_test-context-worker-service.md +48 -0
- package/doc/src_ado-client.md +55 -0
- package/doc/src_logger.md +183 -0
- package/doc/src_mail-service.md +54 -0
- package/doc/src_pdf-generator.md +35 -0
- package/doc/src_test-context.md +173 -0
- package/doc/toc.md +22 -0
- package/eslint.config.js +100 -0
- package/jsconfig.json +14 -0
- package/package.json +58 -0
- package/src/ado-client.js +96 -0
- package/src/index.js +8 -0
- package/src/logger.js +125 -0
- package/src/mail-service.js +81 -0
- package/src/page-objects/guerrillamail.page.js +144 -0
- package/src/page-objects/index.js +3 -0
- package/src/page-objects/maildrop.page.js +97 -0
- package/src/page-objects/page.js +463 -0
- package/src/page-objects/yopmail.page.js +102 -0
- package/src/pdf-generator.js +61 -0
- package/src/test-context.js +248 -0
- package/src/types.d.ts +60 -0
- package/src/wdio/ado-reporter.js +111 -0
- package/src/wdio/test-context-worker-service.js +88 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Test Context** to be populated and passed to WDIO specs.
|
|
3
|
+
* @module
|
|
4
|
+
* @category public
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
import { faker } from '@faker-js/faker/locale/fr';
|
|
11
|
+
import merge from 'lodash.merge';
|
|
12
|
+
import { SevereServiceError } from 'webdriverio';
|
|
13
|
+
|
|
14
|
+
import { MailService } from './mail-service.js';
|
|
15
|
+
import { logger } from './logger.js';
|
|
16
|
+
|
|
17
|
+
/** @typedef {import('./types.js').TestEnvironmentConfiguration} TestEnvironmentConfiguration */
|
|
18
|
+
/** @typedef {import('./types.js').TestUser} TestUser */
|
|
19
|
+
/** @typedef {import('./types.js').MailProviderName} MailProviderName */
|
|
20
|
+
/** @typedef {import('./types.js').DataGenerator} DataGenerator */
|
|
21
|
+
/** @typedef {import('./types.js').LogLevel} LogLevel */
|
|
22
|
+
|
|
23
|
+
export const defaultDatasetName = 'default';
|
|
24
|
+
const defaultRecordedUsersFilename = 'users.json';
|
|
25
|
+
/** @type {MailProviderName} */ const defaultMailProviderName = 'maildrop';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Implements **Test Context** with data to pass to WDIO specs.
|
|
29
|
+
*/
|
|
30
|
+
export class TestContext {
|
|
31
|
+
/** Test user @type {TestUser} */ user;
|
|
32
|
+
/** Mail service @type {MailService} */ mailService;
|
|
33
|
+
|
|
34
|
+
/** Test environments @private @type {Map<string, TestEnvironmentConfiguration>} */
|
|
35
|
+
_environments = new Map();
|
|
36
|
+
/** Mail provider name @private @type {MailProviderName} */ _mailProviderName;
|
|
37
|
+
/** data folder @private @type {string} */ _dataFolder;
|
|
38
|
+
/** log folder @private @type {string} */ _logFolder;
|
|
39
|
+
/** data folder @private @type {string} */ _recordedUsersFilepath;
|
|
40
|
+
/** custom data generator @private @type {DataGenerator} */ _dataGenerator;
|
|
41
|
+
/** default dataset name @private @type {string} */ _defaultDatasetName;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Implements a test context with environments and dataset in webdriverio.
|
|
45
|
+
* @param {string} name name of the environment (defined by TARGET_ENV environment variable)
|
|
46
|
+
* @param {Record<string, any>} parameters custom parameters (like 'url')
|
|
47
|
+
* @param {import('./types.js').EnvironmentOptions} [environmentOptions] custom options
|
|
48
|
+
* @returns {TestContext}
|
|
49
|
+
*/
|
|
50
|
+
setEnvironment(name, parameters, environmentOptions) {
|
|
51
|
+
const options = environmentOptions ?? { perEnvironmentData: false };
|
|
52
|
+
this._environments.set(name, { parameters: new Map(Object.entries(parameters)), options });
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Defines a custom function that adds data to the current dataset
|
|
58
|
+
* @param {DataGenerator} dataGenerator
|
|
59
|
+
* @returns {TestContext}
|
|
60
|
+
*/
|
|
61
|
+
setDataGenerator(dataGenerator) {
|
|
62
|
+
this._dataGenerator = dataGenerator;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Defines the **default dataset** when no DATASET environment variable is specified.
|
|
68
|
+
* @param {string} name name ot the default dataset
|
|
69
|
+
* @returns {TestContext}
|
|
70
|
+
*/
|
|
71
|
+
setDefaultDataset(name) {
|
|
72
|
+
this._defaultDatasetName = name ?? defaultDatasetName;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Defines the **disposable email provider**. Overriden by MAILPROVIDER environment variable. **maildrop** is default value.
|
|
78
|
+
* @param {MailProviderName} name name of the disposable mail service provider
|
|
79
|
+
* @returns {TestContext}
|
|
80
|
+
*/
|
|
81
|
+
setMailProvider(name) {
|
|
82
|
+
this._mailProviderName = /** @type {import('./types.js').MailProviderName} */ (
|
|
83
|
+
process.env.MAILPROVIDER ?? name
|
|
84
|
+
);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Sets the logging level.
|
|
90
|
+
* @param {LogLevel} level logging level
|
|
91
|
+
*/
|
|
92
|
+
setLogLevel(level) {
|
|
93
|
+
logger.setLogLevel(level);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initializes the data of the test context.
|
|
98
|
+
* @returns {TestContext}
|
|
99
|
+
*/
|
|
100
|
+
initialize() {
|
|
101
|
+
// email provider
|
|
102
|
+
this.mailService = new MailService(this._mailProviderName ?? defaultMailProviderName);
|
|
103
|
+
// data folder
|
|
104
|
+
this._dataFolder =
|
|
105
|
+
(this.environment.options.perEnvironmentData ?? false)
|
|
106
|
+
? path.join(process.cwd(), 'data', this.environmentName)
|
|
107
|
+
: path.join(process.cwd(), 'data');
|
|
108
|
+
this._logFolder = path.join(process.cwd(), 'data', 'log');
|
|
109
|
+
this._recordedUsersFilepath = path.join(this._logFolder, defaultRecordedUsersFilename);
|
|
110
|
+
const datasetFilepath = path.join(this._dataFolder, `${this.dataset}.json`);
|
|
111
|
+
if (!fs.existsSync(datasetFilepath))
|
|
112
|
+
throw new SevereServiceError(`Unable to find dataset file '${datasetFilepath}'!`);
|
|
113
|
+
// generate random user
|
|
114
|
+
const user = this._generateRandomUser(this.mailService);
|
|
115
|
+
// generate custom data
|
|
116
|
+
const customData = this._dataGenerator(this);
|
|
117
|
+
// load dataset
|
|
118
|
+
const data = JSON.parse(fs.readFileSync(datasetFilepath, 'utf8'));
|
|
119
|
+
// add dataset to test context
|
|
120
|
+
merge(this, data);
|
|
121
|
+
// add generated user to test context
|
|
122
|
+
merge(this, { user });
|
|
123
|
+
// add custom data to test context
|
|
124
|
+
merge(this, customData);
|
|
125
|
+
// EMAIL environment variable to force a previous recorder user
|
|
126
|
+
const email = process.env.EMAIL;
|
|
127
|
+
if (email) {
|
|
128
|
+
merge(this, { user: this._getPreviouslyRecordedUser(email) });
|
|
129
|
+
}
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Retrieves the specified parameter of the current environment.
|
|
135
|
+
* @param {string} name
|
|
136
|
+
* @param {boolean} [mandatory]
|
|
137
|
+
* @returns {any | undefined}
|
|
138
|
+
*/
|
|
139
|
+
getParameter(name, mandatory) {
|
|
140
|
+
if (mandatory && !this.environment.parameters.has(name))
|
|
141
|
+
throw new Error(`Parameter '${name}' was not defined for '${this.environmentName}' environment!`);
|
|
142
|
+
return this.environment.parameters.get(name);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Retrieves the name of the current environment.
|
|
147
|
+
* @returns {string}
|
|
148
|
+
*/
|
|
149
|
+
get environmentName() {
|
|
150
|
+
if (this._environments.size === 0)
|
|
151
|
+
throw new SevereServiceError('At least one environment must be set! Check your environment.js file.');
|
|
152
|
+
const firstEnvironmentName = this._environments.keys().next().value;
|
|
153
|
+
return (process.env.TARGET_ENV || firstEnvironmentName).toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Retrieves the configuration of the current environment.
|
|
158
|
+
* @returns {TestEnvironmentConfiguration}
|
|
159
|
+
*/
|
|
160
|
+
get environment() {
|
|
161
|
+
return this._environments.get(this.environmentName);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The current dataset.
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
get dataset() {
|
|
169
|
+
return process.env.DATASET || this._defaultDatasetName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Is the current dataset the default one.
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
get isDefaultDataset() {
|
|
177
|
+
return this._defaultDatasetName === this.dataset;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generates a random user.
|
|
182
|
+
* @private
|
|
183
|
+
* @param {MailService} mailService
|
|
184
|
+
* @returns {TestUser}
|
|
185
|
+
*/
|
|
186
|
+
_generateRandomUser(mailService) {
|
|
187
|
+
if (!process.env.USER_PASSWORD)
|
|
188
|
+
throw new SevereServiceError(
|
|
189
|
+
`The default password for generated user was not provided with the 'USER_PASSWORD' environment variable!`
|
|
190
|
+
);
|
|
191
|
+
// random user
|
|
192
|
+
const sex = /** @type {'male' | 'female'} */ (faker.person.sexType());
|
|
193
|
+
const gender = sex === 'female' ? 'Madame' : 'Monsieur';
|
|
194
|
+
const lastname = faker.person.lastName().toUpperCase();
|
|
195
|
+
const firstname = faker.person.firstName(sex);
|
|
196
|
+
// random email
|
|
197
|
+
const domain = faker.helpers.arrayElement(mailService.domains);
|
|
198
|
+
const suffix = new Date().getMilliseconds();
|
|
199
|
+
const email = `${firstname[0]}${lastname}-${suffix}@${domain}`
|
|
200
|
+
.normalize('NFD')
|
|
201
|
+
.replaceAll(/[\u0300-\u036F]/g, '')
|
|
202
|
+
.replaceAll(new RegExp(' ', 'g'), '')
|
|
203
|
+
.toLowerCase();
|
|
204
|
+
return { gender, lastname, firstname, email, password: process.env.USER_PASSWORD };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Retrieves the previously recorded user.
|
|
209
|
+
* @private
|
|
210
|
+
* @param {string} email
|
|
211
|
+
* @returns {TestUser}
|
|
212
|
+
*/
|
|
213
|
+
_getPreviouslyRecordedUser(email) {
|
|
214
|
+
/** @type {import('./types.js').RecordedTestUser[]} */
|
|
215
|
+
const users = JSON.parse(fs.readFileSync(this._recordedUsersFilepath, 'utf8'));
|
|
216
|
+
const lastUser = email?.toLowerCase() === 'last';
|
|
217
|
+
const previouslyRecordedUser = lastUser
|
|
218
|
+
? users.findLast(a => a.environment === this.environmentName)
|
|
219
|
+
: users.findLast(a => a.email === email && a.environment === this.environmentName);
|
|
220
|
+
if (!previouslyRecordedUser) {
|
|
221
|
+
const userInfo = lastUser ? 'last recorded user' : `previously recorded user with email '${email}'`;
|
|
222
|
+
throw new SevereServiceError(
|
|
223
|
+
`Unable to find ${userInfo} for '${this.environmentName}' in the ${defaultRecordedUsersFilename} file!`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
delete previouslyRecordedUser.dataset;
|
|
227
|
+
delete previouslyRecordedUser.date;
|
|
228
|
+
delete previouslyRecordedUser.environment;
|
|
229
|
+
return previouslyRecordedUser;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Records the current created user.
|
|
234
|
+
*/
|
|
235
|
+
recordTestUser() {
|
|
236
|
+
if (!fs.existsSync(this._recordedUsersFilepath)) {
|
|
237
|
+
fs.mkdirSync(this._logFolder, { recursive: true });
|
|
238
|
+
fs.writeFileSync(this._recordedUsersFilepath, JSON.stringify([]));
|
|
239
|
+
}
|
|
240
|
+
const { firstname, lastname, gender, email, password } = this.user;
|
|
241
|
+
const environment = this.environmentName;
|
|
242
|
+
const dataset = this.dataset;
|
|
243
|
+
const date = new Date().toLocaleString('fr-FR');
|
|
244
|
+
const users = JSON.parse(fs.readFileSync(this._recordedUsersFilepath, 'utf8'));
|
|
245
|
+
users.push({ environment, dataset, date, lastname, firstname, gender, email, password });
|
|
246
|
+
fs.writeFileSync(this._recordedUsersFilepath, JSON.stringify(users));
|
|
247
|
+
}
|
|
248
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TestContext } from './test-context';
|
|
2
|
+
|
|
3
|
+
export { TestContext };
|
|
4
|
+
|
|
5
|
+
/** TestContextWorkerService options */
|
|
6
|
+
export type TestContextWorkerServiceOptions = {
|
|
7
|
+
testContext: TestContext;
|
|
8
|
+
logLevel?: LogLevel;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** User gender. */
|
|
12
|
+
export type Gender = 'Madame' | 'Monsieur';
|
|
13
|
+
|
|
14
|
+
/** Implemented disposable email provider */
|
|
15
|
+
export type MailProviderName = 'yopmail' | 'maildrop' | 'guerrillamail';
|
|
16
|
+
|
|
17
|
+
/** Disposable email provider */
|
|
18
|
+
export type MailProvider = {
|
|
19
|
+
/** name of the provider */ name: string;
|
|
20
|
+
/** domains exposed of the provider */ domains: string[];
|
|
21
|
+
/** wdio logic to open the inbox */ openInbox: (email?: string) => Promise<void>;
|
|
22
|
+
/** wdio logic to get emails */ getMessages: () => Promise<{ from: string; subject: string }[]>;
|
|
23
|
+
/** wdio logic to retieve email content */ getMessageContent: (index: number) => Promise<string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Randomly auto generated user */
|
|
27
|
+
export type TestUser = {
|
|
28
|
+
/** last name */ lastname: string;
|
|
29
|
+
/** first name */ firstname: string;
|
|
30
|
+
/** gender */ gender: Gender;
|
|
31
|
+
/** e-mail */ email: string;
|
|
32
|
+
/** password */ password: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Recorded generated user */
|
|
36
|
+
export type RecordedTestUser = TestUser & {
|
|
37
|
+
/** target environment used for recording */ environment: string;
|
|
38
|
+
/** dataset used for recording */ dataset: string;
|
|
39
|
+
/** recording date */ date: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Log level */
|
|
43
|
+
export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
44
|
+
|
|
45
|
+
/** Test environment configuration*/
|
|
46
|
+
export type TestEnvironmentConfiguration = {
|
|
47
|
+
/** parameters of the environment */ parameters: Map<string, any>;
|
|
48
|
+
/** options of the environment */ options: EnvironmentOptions;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Custom options for the environement */
|
|
52
|
+
export type EnvironmentOptions = {
|
|
53
|
+
/** Retrieve dataset **per environment** rather than globally. Dataset are global if not specified. */
|
|
54
|
+
perEnvironmentData?: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Generate dynamic data to append to TestContext */
|
|
58
|
+
export type DataGenerator = {
|
|
59
|
+
(context: TestContext): any;
|
|
60
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **WDIO Reporter** to handle Azure DevOps Test Run generation.
|
|
3
|
+
* @module
|
|
4
|
+
* @category public
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { inspect } from 'node:util';
|
|
9
|
+
|
|
10
|
+
import WDIOReporter from '@wdio/reporter';
|
|
11
|
+
|
|
12
|
+
import { createAdoTestRun } from '../ado-client.js';
|
|
13
|
+
import { logger } from '../logger.js';
|
|
14
|
+
import { defaultDatasetName } from '../test-context.js';
|
|
15
|
+
|
|
16
|
+
/** @typedef { import('azure-devops-node-api/interfaces/TestInterfaces.d.ts').TestCaseResult} TestResult */
|
|
17
|
+
|
|
18
|
+
/** Default Azure Devops Test Run model @type {import('../ado-client.js').RunModel} */
|
|
19
|
+
const testRun = {
|
|
20
|
+
name: 'Tests e2e',
|
|
21
|
+
results: [],
|
|
22
|
+
state: 'Completed'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class AdoReporter extends WDIOReporter {
|
|
26
|
+
/**
|
|
27
|
+
* Implements the **WDIO Reporter** that produces an Azure DevOps Test Run.
|
|
28
|
+
*/
|
|
29
|
+
constructor(options) {
|
|
30
|
+
super(options);
|
|
31
|
+
this.isSynchronising = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get isSynchronised() {
|
|
35
|
+
return this.isSynchronising;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Gets executed when the runner starts.
|
|
40
|
+
* @param {import('@wdio/reporter').RunnerStats} run run information
|
|
41
|
+
*/
|
|
42
|
+
onRunnerStart(run) {
|
|
43
|
+
const projet = process.env.SYSTEM_TEAMPROJECT;
|
|
44
|
+
const dataset = process.env.DATASET || defaultDatasetName;
|
|
45
|
+
const environnement = process.env.TARGET_ENV.toUpperCase();
|
|
46
|
+
const buildId = process.env.BUILD_BUILDID;
|
|
47
|
+
testRun.startDate = new Date().toISOString();
|
|
48
|
+
testRun.name = `Tests e2e ${projet} | Scénario '${dataset}' sur ${environnement}`;
|
|
49
|
+
testRun.comment = `Executé avec ${run.capabilities.browserName}(${run.capabilities.browserVersion}) | ${run.capabilities.platformName}`;
|
|
50
|
+
testRun.build = { id: buildId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Gets executed when the test passes.
|
|
55
|
+
* @param {import('@wdio/reporter').TestStats} test test information
|
|
56
|
+
*/
|
|
57
|
+
onTestPass(test) {
|
|
58
|
+
testRun.results.push({
|
|
59
|
+
automatedTestName: test.fullTitle,
|
|
60
|
+
testCaseTitle: test.fullTitle,
|
|
61
|
+
startedDate: test.start,
|
|
62
|
+
completedDate: test.end,
|
|
63
|
+
outcome: 'Passed',
|
|
64
|
+
state: 'Completed'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets executed when the test fails.
|
|
70
|
+
* @param {import('@wdio/reporter').TestStats} test test information
|
|
71
|
+
*/
|
|
72
|
+
onTestFail(test) {
|
|
73
|
+
testRun.state = 'Aborted';
|
|
74
|
+
testRun.results.push({
|
|
75
|
+
automatedTestName: test.fullTitle,
|
|
76
|
+
testCaseTitle: test.fullTitle,
|
|
77
|
+
startedDate: test.start,
|
|
78
|
+
completedDate: test.end,
|
|
79
|
+
outcome: test.error.stack === undefined ? 'Failed' : 'Error',
|
|
80
|
+
errorMessage: test.error.message,
|
|
81
|
+
stackTrace: test.error.stack,
|
|
82
|
+
state: 'Completed'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets executed when the test is skipped.
|
|
88
|
+
* @param {import('@wdio/reporter').TestStats} test test information
|
|
89
|
+
*/
|
|
90
|
+
onTestSkip(test) {
|
|
91
|
+
testRun.results.push({
|
|
92
|
+
automatedTestName: test.fullTitle,
|
|
93
|
+
testCaseTitle: test.fullTitle,
|
|
94
|
+
startedDate: test.start,
|
|
95
|
+
completedDate: test.end,
|
|
96
|
+
outcome: 'NotExecuted',
|
|
97
|
+
state: 'Completed'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Gets executed when the runner ends.
|
|
103
|
+
* @param {import('@wdio/reporter').RunnerStats} _run run information
|
|
104
|
+
*/
|
|
105
|
+
async onRunnerEnd(_run) {
|
|
106
|
+
testRun.completedDate = new Date().toISOString();
|
|
107
|
+
logger.debug(inspect(testRun, { colors: true, depth: 5 }));
|
|
108
|
+
await createAdoTestRun(testRun);
|
|
109
|
+
this.isSynchronising = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* global browser */
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import { inspect } from 'node:util';
|
|
6
|
+
|
|
7
|
+
import moment from 'moment';
|
|
8
|
+
import { SevereServiceError } from 'webdriverio';
|
|
9
|
+
|
|
10
|
+
import { logger } from '../logger.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('@wdio/types/build/Options.d.ts').Testrunner } TestrunnerOptions */
|
|
13
|
+
/** @typedef {import('@wdio/types/build/Capabilities.d.ts').TestrunnerCapabilities} TestrunnerCapabilities */
|
|
14
|
+
/** @typedef {import('@wdio/types/build/Frameworks.d.ts').Test} Test */
|
|
15
|
+
/** @typedef {import('@wdio/types/build/Frameworks.d.ts').TestResult} TestResult */
|
|
16
|
+
/** @typedef {import('../types.js').TestContextWorkerServiceOptions} TestContextWorkerServiceOptions */
|
|
17
|
+
/** @typedef {import('../types.js').LogLevel} LogLevel */
|
|
18
|
+
|
|
19
|
+
/** @type {(object: any) => any } */ const filterPrivateProperty = object =>
|
|
20
|
+
Object.fromEntries(Object.entries(object).filter(([key]) => !key.startsWith('_') && key !== 'mailService'));
|
|
21
|
+
|
|
22
|
+
export default class TestContextWorkerService {
|
|
23
|
+
/**
|
|
24
|
+
* Imlements the **WDIO Worker Service** that handles the Test Context life cycle.
|
|
25
|
+
* @param {TestContextWorkerServiceOptions} serviceOptions
|
|
26
|
+
* @param {TestrunnerCapabilities} _capabilities
|
|
27
|
+
* @param {TestrunnerOptions} _config
|
|
28
|
+
*/
|
|
29
|
+
constructor(serviceOptions, _capabilities, _config) {
|
|
30
|
+
const { testContext, logLevel } = serviceOptions;
|
|
31
|
+
if (!testContext)
|
|
32
|
+
throw new SevereServiceError(
|
|
33
|
+
'No context specified in TestContextWorkerService options! This is a required parameter.'
|
|
34
|
+
);
|
|
35
|
+
this._logLevel = logLevel ?? 'error';
|
|
36
|
+
logger.setLogLevel(this._logLevel);
|
|
37
|
+
this._context = testContext;
|
|
38
|
+
this._context.setLogLevel(this._logLevel);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets executed just before initialising the webdriver session and test framework.
|
|
43
|
+
* **Displays** the Test Context.
|
|
44
|
+
* @param {TestrunnerOptions} _config
|
|
45
|
+
* @param {TestrunnerCapabilities} _capabilities
|
|
46
|
+
* @param {string[]} _specs
|
|
47
|
+
* @param {string} _cid
|
|
48
|
+
*/
|
|
49
|
+
beforeSession(_config, _capabilities, _specs, _cid) {
|
|
50
|
+
logger.debug(
|
|
51
|
+
inspect(filterPrivateProperty(this._context), {
|
|
52
|
+
colors: true,
|
|
53
|
+
depth: 5
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets executed after a test.
|
|
60
|
+
* Generates **error screenshot** if applicable.
|
|
61
|
+
* @param {Test} test
|
|
62
|
+
* @param {*} _context
|
|
63
|
+
* @param {TestResult} result
|
|
64
|
+
*/
|
|
65
|
+
async afterTest(test, _context, result) {
|
|
66
|
+
const { passed, error: testError } = result;
|
|
67
|
+
if (passed || !testError || !this._context) return;
|
|
68
|
+
const { dataset, environmentName } = this._context;
|
|
69
|
+
try {
|
|
70
|
+
const folder = path.join(process.cwd(), 'screenshot', environmentName, dataset);
|
|
71
|
+
await fs.mkdir(folder, { recursive: true });
|
|
72
|
+
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
|
|
73
|
+
const filename = path.join(folder, `error-${timestamp}`);
|
|
74
|
+
const { message, stack } = testError;
|
|
75
|
+
const errorLines = stack?.split('at ') ?? [];
|
|
76
|
+
const errorFile = errorLines
|
|
77
|
+
.find((/** @type {string} */ l) => l.toLowerCase().includes('e2e.js'))
|
|
78
|
+
.trim();
|
|
79
|
+
const fileContent = `Test : ${test.fullName}\nErreur : ${message}${
|
|
80
|
+
errorFile ? '\nEmplacement : ' + errorFile : ''
|
|
81
|
+
}`;
|
|
82
|
+
await fs.writeFile(`${filename}.txt`, fileContent, { encoding: 'utf8', flag: 'w', flush: true });
|
|
83
|
+
await browser.saveScreenshot(`${filename}.png`);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.detailError(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|