@actionlogementservices/wdio-test-context 1.0.1 → 1.1.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/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # wdio-test-context
2
+
2
3
  A framework to add test context to webdriverio end to end tests.
4
+
3
5
  - It uses JSON files (named dataset) and optional javascript function to add dynamic content to your dataset.
4
6
  - It consumes a disposable mail service (YOPmail, maildrop or Guerilla Mail) in order to check for new message.
5
7
  - It provides pdf generation to enable document uploads.
6
8
  - It provides an Azure DevOps webdriverio reporter.
7
9
  - It generates a screenshot when the test fails.
8
10
 
9
- ## Installation
11
+ ## Installation
10
12
 
11
13
  - In your webdriverio test project:
12
14
 
@@ -24,7 +26,7 @@ A framework to add test context to webdriverio end to end tests.
24
26
 
25
27
  ```js
26
28
  // import your own function to generate dynamic data
27
- import { generateRandomData } from './generate-random-data.js';
29
+ import { generateRandomData } from './generate-random-data.js';
28
30
  import { TestContext } from '@actionlogementservices/wdio-test-context';
29
31
 
30
32
  // to enable intellisense with your own data
@@ -36,16 +38,17 @@ A framework to add test context to webdriverio end to end tests.
36
38
  .setDataGenerator(generateRandomData) // optional : add your own dynamic data
37
39
  .setDefaultDataset('test') // default dataset name when DATASET env var is not specified
38
40
  .setEnvironment('local', {
41
+ //define parameters for your environment
39
42
  frontUrl: 'https://test.lvh.me',
40
43
  backOfficeUrl: 'https://test-backoffice.lvh.me:4432',
41
- sso: true,
44
+ sso: true
42
45
  })
43
46
  .setEnvironment('staging', {
44
47
  frontUrl: 'https://staging.domain.com',
45
- backOfficeUrl: 'https://staging-backoffice.domain.com',
48
+ backOfficeUrl: 'https://staging-backoffice.domain.com'
46
49
  })
47
50
  .initialize()
48
- );
51
+ );
49
52
  ```
50
53
 
51
54
  - Create optionnally `data/generate-random-data.js` to add dynamic data, file with the following code:
@@ -61,16 +64,41 @@ A framework to add test context to webdriverio end to end tests.
61
64
  // dates random
62
65
  const customDate = faker.date.between({ from: '1995-01-01', to: '2001-01-01' });
63
66
  return {
64
- customDate,
67
+ customDate
65
68
  };
66
69
  }
67
70
 
68
71
  export { generateRandomData };
69
72
  ```
70
73
 
74
+ - Configure optionnaly a **RabbitMQ client** if you plan to send some RabbitMQ message within your tests:
75
+
76
+ > **NOTE**
77
+ >
78
+ > - If you have secrets in your connection string, you need to
79
+ >
80
+ > - create a `.env.{your_environment_name}.local` file (for instance `.env.staging.local`) with your secret configuration parameters.
81
+ > - add the following entries to your `.gitignore`:
82
+ >
83
+ > `.env.local`<br>`.env.*.local`
84
+
85
+ ```js
86
+ export const testContext = /** @type {TestContextModel & TestContext} */ (
87
+ new TestContext()
88
+ .setEnvironment(
89
+ 'local',
90
+ {
91
+ frontUrl: 'https://test.lvh.me',
92
+ },
93
+ {
94
+ amqp: String.raw`amqps://${process.env.RMQ_USER}:${process.env.RMQ_PWD}@your-host:5671/your-vhost`,
95
+ }
96
+ )
97
+ ```
98
+
71
99
  - Configure webdriverio: in your wdio.conf.js:
72
100
 
73
- ```js
101
+ ```js
74
102
  import { AdoReporter, TestContextWorkerService } from '@actionlogementservices/wdio-test-context';
75
103
  import { testContext } from './data/test-context.js';
76
104
 
@@ -79,25 +107,158 @@ A framework to add test context to webdriverio end to end tests.
79
107
  reporters: [[AdoReporter, {}]],
80
108
  services: [[TestContextWorkerService, { testContext, logLevel: 'debug' }]],
81
109
  ...
110
+ }
111
+ ```
112
+
113
+ ## Notes on secrets
114
+
115
+ If you have **secrets** in your parameter values, you need to
116
+
117
+ - create a `.env.{your_environment_name}.local` file (for instance `.env.staging.local`) with your secret configuration parameters:
118
+
119
+ ```sh
120
+ DB_PWD="MySyperStrongPassword"
121
+ CLIENT_SECRET="MySuperSafeSecret"
122
+ ```
123
+
124
+ - references the secret in the environment.js like this:
125
+
126
+ ```js
127
+ .setEnvironment('staging', {
128
+ sql: 'Server=YourServer;Password=${process.env.DB_PWD};...'
129
+ ```
130
+
131
+ - to add the following entries to your `.gitignore` to prevent storing secret values in your repository:
132
+
133
+ ```sh
134
+ .env.local
135
+ .env.*.local
82
136
  ```
83
137
 
84
138
  ## Use the test context in the spec file
85
139
 
86
- - Somewhere in your spec files:
140
+ ### To access the mail service
141
+
142
+ - Access the `mailService` property:
87
143
 
88
144
  ```js
89
145
  import { testContext } from '../../environments.js';
90
146
 
91
147
  describe(`[Inbox] - ${testContext.user.email} - I`, () => {
92
148
  let confirmationLink;
93
- it('receive a mail with confirmation code within 10s', async () => {
94
- await browser.open(testContext.getParameter('frontUrl')); // to retrieve parameters
95
- const from = 'no-reply@domain.com';
96
- const subject = `Confirmation code`;
97
- // to wait for email and to access test context data
98
- const content = await testContext.mailService.waitForMessage(testContext.user.email, from, subject);
99
- confirmationCode = getConfirmationCode(content);
100
- expect(confirmationCode).toContain('123456');
149
+ it('receive a mail with confirmation code within 10s', async () => {
150
+ await browser.open(testContext.getParameter('frontUrl')); // to retrieve parameters
151
+ const from = 'no-reply@domain.com';
152
+ const subject = `Confirmation code`;
153
+ // to wait for email and to access test context data
154
+ const content = await testContext.mailService.waitForMessage(testContext.user.email, from, subject);
155
+ confirmationCode = getConfirmationCode(content);
156
+ expect(confirmationCode).toContain('123456');
157
+ });
158
+ }
159
+ ```
160
+
161
+ ### To execute SQL queries
162
+
163
+ - You need to define a **parameter** in `your environment.js` that contains the **sql connection string**. In the following exemple it is named `sqlBO`.
164
+
165
+ ```js
166
+ export const testContext = /** @type {TestContextModel & TestContext} */ (
167
+ new TestContext()
168
+ .setEnvironment(
169
+ 'local',
170
+ {
171
+ frontUrl: 'https://test.lvh.me',
172
+ sqlBO: String.raw`Driver={ODBC Driver 17 for SQL Server};Server=(localdb)\MSSQLLocalDB;Database=YourDatabase;Trusted_Connection=yes;`,
173
+ }
174
+ )
175
+ ```
176
+
177
+ > **NOTES on SQL connection string**
178
+ >
179
+ > - If you plan to use `(localdb)` or **Windows authentication** this requires an **ODBC driver** with a specific connection string syntax : `Driver={...}`. You also need to check which Microsoft SQL Server **ODBC driver** is installed on your system with the `ODBC Data Sources` control panel.
180
+ > - For Microsoft Sql Server instance without Windows Authentication, there is no need to use an ODBC driver. In such case remove the `Driver={...}` portion of the connection string.
181
+ > - You **can not** mix both syntax in the same environment.
182
+ > - In the previous example, `(localdb)` is used with the `ODBC Driver 17 for SQL Server` which is installed.
183
+
184
+ - Somewhere in you spec files
185
+
186
+ ```js
187
+ import { testContext } from '../../environments.js';
188
+
189
+ describe(`Before starting the test, we`, () => {
190
+
191
+ it('check the sql version', async () => {
192
+ const result = await testContext.executeSql('sqlBO', (sql) => sql.query('SELECT @@Version'));
193
+ ...
194
+ });
195
+ ```
196
+
197
+ ### To publish AMQP message
198
+
199
+ - You need to define a **parameter** in `your environment.js` that contains the **amqp connection string**. In the following exemple it is named `amqpBO`.
200
+
201
+ ```js
202
+ export const testContext = /** @type {TestContextModel & TestContext} */ (
203
+ new TestContext()
204
+ .setEnvironment(
205
+ 'local',
206
+ {
207
+ frontUrl: 'https://test.lvh.me',
208
+ amqpBO: String.raw`Driver={ODBC Driver 17 for SQL Server};Server=(localdb)\MSSQLLocalDB;Database=YourDatabase;Trusted_Connection=yes;`,
209
+ }
210
+ )
211
+ ```
212
+
213
+ - Somewhere in you spec files
214
+
215
+ ```js
216
+ import { testContext } from '../../environments.js';
217
+
218
+ describe(`Before starting the test, we`, () => {
219
+
220
+ it('send an amqp message', async () => {
221
+ const result = await testContext.publishMessage('amqpBO',
222
+ 'exchange', // your exchange name
223
+ 'routingKey', // your routing key
224
+ 'type', // your message type
225
+ { test: true }, // your message payload
226
+ testContext.user.email // something to correlate/track you message
227
+ );
228
+ expect(result).toBe(true);
229
+ });
230
+ ```
231
+
232
+ ### To run a VTOM job
233
+
234
+ - You need to define a **parameter** in `your environment.js` that contains the **VTOM configuration**. In the following exemple it is named `vtomBO`.
235
+
236
+ ```js
237
+ export const testContext = /** @type {TestContextModel & TestContext} */ (
238
+ new TestContext()
239
+ .setEnvironment(
240
+ 'local',
241
+ {
242
+ frontUrl: 'https://test.lvh.me',
243
+ vtomBO: {
244
+ url: 'https://...',
245
+ apiKey: 'abcdef',
246
+ environement: 'DEV'
247
+ },
248
+ }
249
+ )
250
+ ```
251
+
252
+ - Somewhere in you spec files
253
+
254
+ ```js
255
+ import { testContext } from '../../environments.js';
256
+
257
+ describe(`Before starting the test, we`, () => {
258
+
259
+ it('run a vtom job', async () => {
260
+ const result = await testContext.runVtomJob('vtomBO', 'app', 'job');
261
+ expect(result).toBe(true);
101
262
  });
102
263
  ```
103
264
 
@@ -128,7 +128,7 @@ __*return*__ | `Promise.<string>` | *the corresponding inner html*
128
128
 
129
129
  ---
130
130
 
131
- ### `getOptions(css, timeout) ► Promise.<Array.<string>>`
131
+ ### `getSelectOptions(css, childrenCount, timeout) ► Promise.<Array.<string>>`
132
132
 
133
133
  ![modifier: public](images/badges/modifier-public.png)
134
134
 
@@ -138,12 +138,13 @@ Waits during the specified (or default) timeout **before throwing error**.
138
138
  Parameters | Type | Description
139
139
  --- | --- | ---
140
140
  __css__ | `string` | *css selector of the select*
141
+ __childrenCount__ | `number` | *optional count of children to wait for (default 1)*
141
142
  __timeout__ | `number` | *optional timeout before throwing error*
142
143
  __*return*__ | `Promise.<Array.<string>>` | *the corresponding values*
143
144
 
144
145
  ---
145
146
 
146
- ### `getSelectedText(css, timeout) ► Promise.<string>`
147
+ ### `getSelectSelectedText(css, childrenCount, timeout) ► Promise.<string>`
147
148
 
148
149
  ![modifier: public](images/badges/modifier-public.png)
149
150
 
@@ -153,12 +154,13 @@ Waits during the specified (or default) timeout **before throwing error**.
153
154
  Parameters | Type | Description
154
155
  --- | --- | ---
155
156
  __css__ | `string` | *css selector of the select*
157
+ __childrenCount__ | `number` | *optional count of children to wait for (default 1)*
156
158
  __timeout__ | `number` | *optional timeout before throwing error*
157
159
  __*return*__ | `Promise.<string>` | *the corresponding text*
158
160
 
159
161
  ---
160
162
 
161
- ### `getSelectedValue(css, timeout) ► Promise.<string>`
163
+ ### `getSelectSelectedValue(css, childrenCount, timeout) ► Promise.<string>`
162
164
 
163
165
  ![modifier: public](images/badges/modifier-public.png)
164
166
 
@@ -168,6 +170,7 @@ Waits during the specified (or default) timeout **before throwing error**.
168
170
  Parameters | Type | Description
169
171
  --- | --- | ---
170
172
  __css__ | `string` | *css selector of the select*
173
+ __childrenCount__ | `number` | *optional count of children to wait for (default 1)*
171
174
  __timeout__ | `number` | *optional timeout before throwing error*
172
175
  __*return*__ | `Promise.<string>` | *the corresponding text*
173
176
 
@@ -335,7 +338,7 @@ __*return*__ | `Promise.<boolean>` | *true if condition is fullfilled*
335
338
 
336
339
  Parameters | Type | Description
337
340
  --- | --- | ---
338
- __css__ | `string` | *css selector*
341
+ __css__ | `string` | *css selector that contains children*
339
342
  __childrenCount__ | `number` | *optional count of children to wait for (default 1)*
340
343
  __timeout__ | `number` | *optional timeout before throwing error*
341
344
  __*return*__ | `Promise.<boolean>` | *true if condition is fullfilled*
@@ -0,0 +1,28 @@
1
+ # Module `amqp-client`
2
+
3
+ ![category:private](https://img.shields.io/badge/category-private-blue.svg?style=flat-square)
4
+
5
+ **Amqp client** to send message on a Amqp broker.
6
+ Accessible through the **publishMessage** method of the TestContext instance.
7
+
8
+ [Source file](../src/amqp-client.js)
9
+
10
+ ## Functions
11
+
12
+ ### `publishMessage(connectionString, exchange, routingKey, type, payload, correlationId) ► Promise.<boolean>`
13
+
14
+ ![modifier: public](images/badges/modifier-public.png) ![modifier: static](images/badges/modifier-static.png)
15
+
16
+ Publishes the message to the specified exchange of the specified amqp server.
17
+
18
+ Parameters | Type | Description
19
+ --- | --- | ---
20
+ __connectionString__ | `string` | *connection string*
21
+ __exchange__ | `string` | *name of the exchange*
22
+ __routingKey__ | `string` | *routing key*
23
+ __type__ | `string` | *type of the message*
24
+ __payload__ | `T` | *message payload*
25
+ __correlationId__ | `string` | *correlation id for message tracking*
26
+ __*return*__ | `Promise.<boolean>` | **
27
+
28
+ ---
@@ -0,0 +1,17 @@
1
+ # Module `ca-loader`
2
+
3
+ ![category:internal](https://img.shields.io/badge/category-internal-009663.svg?style=flat-square)
4
+
5
+ CA certificates loader.
6
+
7
+ [Source file](../src/ca-loader.js)
8
+
9
+ ## Functions
10
+
11
+ ### `loadCACertificates()`
12
+
13
+ ![modifier: public](images/badges/modifier-public.png) ![modifier: static](images/badges/modifier-static.png)
14
+
15
+ Loads CA certificats if there are present in a ca folder.
16
+
17
+ ---
@@ -0,0 +1,24 @@
1
+ # Module `sql-client`
2
+
3
+ ![category:private](https://img.shields.io/badge/category-private-blue.svg?style=flat-square)
4
+
5
+ **Sql client** to execute query on Miscrosoft SQL Server.
6
+ Accessible through the **executeSql** method of the TestContext instance.
7
+
8
+ [Source file](../src/sql-client.js)
9
+
10
+ ## Functions
11
+
12
+ ### `executeSql(connectionString, requestFunction) ► Promise.<T>`
13
+
14
+ ![modifier: public](images/badges/modifier-public.png) ![modifier: static](images/badges/modifier-static.png)
15
+
16
+ Executes the provided function passing the sql request object.
17
+
18
+ Parameters | Type | Description
19
+ --- | --- | ---
20
+ __connectionString__ | `string` | *sql server connection string*
21
+ __requestFunction__ | `(sql: import('mssql').Request) => T` | *function using the sql request object*
22
+ __*return*__ | `Promise.<T>` | *sql query result*
23
+
24
+ ---
@@ -38,7 +38,7 @@ Parameters | Type | Description
38
38
  --- | --- | ---
39
39
  __name__ | `string` | *name of the environment (defined by TARGET_ENV environment variable)*
40
40
  __parameters__ | `Record.<string, any>` | *custom parameters (like &#x27;url&#x27;)*
41
- __environmentOptions__ | `import('./types.js').EnvironmentOptions` | *custom options*
41
+ __environmentOptions__ | `import('./types.d.ts').EnvironmentOptions` | *custom options (like sql or rabbitMQ clients)*
42
42
  __*return*__ | [TestContext](src_test-context.md) | **
43
43
 
44
44
  ---
@@ -128,6 +128,53 @@ Records the current created user.
128
128
 
129
129
  ---
130
130
 
131
+ ### `publishMessage(parameterName, exchange, routingKey, type, payload, correlationId) ► Promise.<boolean>`
132
+
133
+ ![modifier: public](images/badges/modifier-public.png)
134
+
135
+ Published the amqp message to the specified exchange.
136
+
137
+ Parameters | Type | Description
138
+ --- | --- | ---
139
+ __parameterName__ | `string` | *name of the environment parameter that defines the amqp connection string*
140
+ __exchange__ | `string` | *name of the exchange*
141
+ __routingKey__ | `string` | *routing key*
142
+ __type__ | `string` | *type of the message*
143
+ __payload__ | `T` | *message payload*
144
+ __correlationId__ | `string` | *correlation id for message tracking*
145
+ __*return*__ | `Promise.<boolean>` | **
146
+
147
+ ---
148
+
149
+ ### `executeSql(parameterName, sqlOperation) ► Promise.<T>`
150
+
151
+ ![modifier: public](images/badges/modifier-public.png)
152
+
153
+ Executes the specified sql operation.
154
+
155
+ Parameters | Type | Description
156
+ --- | --- | ---
157
+ __parameterName__ | `string` | *name of the environment parameter that defines the sql connection string*
158
+ __sqlOperation__ | `(sql: import('mssql').Request) => T` | *function using the sql request object*
159
+ __*return*__ | `Promise.<T>` | *sql query result*
160
+
161
+ ---
162
+
163
+ ### `runVtomJob(parameterName, appName, jobName) ► Promise.<boolean>`
164
+
165
+ ![modifier: public](images/badges/modifier-public.png)
166
+
167
+ Runs the specified vtom job.
168
+
169
+ Parameters | Type | Description
170
+ --- | --- | ---
171
+ __parameterName__ | `string` | *name of the environment parameter that defines the vtom configuration*
172
+ __appName__ | `string` | *vtom application name*
173
+ __jobName__ | `string` | *vtom job name*
174
+ __*return*__ | `Promise.<boolean>` | *true if successful execution*
175
+
176
+ ---
177
+
131
178
  ### `_generateRandomUser(mailService) ► TestUser`
132
179
 
133
180
  ![modifier: private](images/badges/modifier-private.png)
@@ -160,6 +207,7 @@ Name | Type | Description
160
207
  --- | --- | ---
161
208
  __user__ | `TestUser` | *Test user @type {TestUser}*
162
209
  __mailService__ | [MailService](src_mail-service.md) | *Mail service @type {MailService}*
210
+ __defaultEnvironmentName__ | `undefined` | *Retrieves the default (i.e. the first) environment name.*
163
211
  __environmentName__ | `undefined` | *Retrieves the name of the current environment.*
164
212
  __environment__ | `undefined` | *Retrieves the configuration of the current environment.*
165
213
  __dataset__ | `undefined` | *The current dataset.*
@@ -0,0 +1,53 @@
1
+ # Module `vtom-client`
2
+
3
+ ![category:private](https://img.shields.io/badge/category-private-blue.svg?style=flat-square)
4
+
5
+ **Vtom client** to run Vtom job.
6
+ Accessible through the **runVtomJob** method of the TestContext instance.
7
+
8
+ [Source file](../src/vtom-client.js)
9
+
10
+ ## Functions
11
+
12
+ ### `wait(ms)`
13
+
14
+ ![modifier: public](images/badges/modifier-public.png)
15
+
16
+
17
+
18
+ Parameters | Type | Description
19
+ --- | --- | ---
20
+ __ms__ | `number` | *number of milliseconds to wait*
21
+
22
+ ---
23
+
24
+ ### `createClient(baseURL, apiKey) ► import('axios').AxiosInstance`
25
+
26
+ ![modifier: public](images/badges/modifier-public.png)
27
+
28
+ Creates the http client.
29
+
30
+ Parameters | Type | Description
31
+ --- | --- | ---
32
+ __baseURL__ | `string` | *api base url*
33
+ __apiKey__ | `string` | *vtom api key*
34
+ __*return*__ | `import('axios').AxiosInstance` | *}*
35
+
36
+ ---
37
+
38
+ ### `runVtomJob(apiUrl, apiKey, environment, appName, jobName) ► Promise.<boolean>`
39
+
40
+ ![modifier: public](images/badges/modifier-public.png) ![modifier: static](images/badges/modifier-static.png)
41
+
42
+ Runs the specified vtom job.
43
+
44
+ Parameters | Type | Description
45
+ --- | --- | ---
46
+ __apiUrl__ | `string` | *vtom base api url*
47
+ __apiKey__ | `string` | *vtom api key*
48
+ __environment__ | `string` | *vtom environment*
49
+ __appName__ | `string` | *vtom application name*
50
+ __jobName__ | `string` | *vtom job name*
51
+ __*return*__ | `Promise.<boolean>` | **
52
+
53
+ ---
package/doc/toc.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Table of contents
4
4
 
5
+ * ![category:internal](https://img.shields.io/badge/category-internal-009663.svg?style=flat-square)
6
+ * [ado-client](src_ado-client.md) - _Azure DevOps rest client to push test results._
7
+ * [ca-loader](src_ca-loader.md) - _CA certificates loader._
8
+ * [logger](src_logger.md) - _Internal logger._
9
+ * [page-objects/guerrillamail.page](src-page-objects_guerrillamail.page.md) - _Page-object on top of Guerilla Mail._
10
+ * [page-objects/maildrop.page](src-page-objects_maildrop.page.md) - _Page-object on top of maildrop._
11
+ * [page-objects/yopmail.page](src-page-objects_yopmail.page.md) - _Page-object on top of YOPmail._
12
+
13
+ * ![category:private](https://img.shields.io/badge/category-private-blue.svg?style=flat-square)
14
+ * [amqp-client](src_amqp-client.md) - _**Amqp client** to send message on a Amqp broker.
15
+ Accessible through the **publishMessage** method of the TestContext instance._
16
+ * [sql-client](src_sql-client.md) - _**Sql client** to execute query on Miscrosoft SQL Server.
17
+ Accessible through the **executeSql** method of the TestContext instance._
18
+ * [vtom-client](src_vtom-client.md) - _**Vtom client** to run Vtom job.
19
+ Accessible through the **runVtomJob** method of the TestContext instance._
20
+
5
21
  * ![category:public](https://img.shields.io/badge/category-public-FF5000.svg?style=flat-square)
6
22
  * [mail-service](src_mail-service.md) - _**Mail service** to send and retrieve mail from various provider.
7
23
  Accessible through the **mailService property** of the TestContext instance._
@@ -10,13 +26,6 @@ Accessible through the **mailService property** of the TestContext instance._
10
26
  * [test-context](src_test-context.md) - _**Test Context** to be populated and passed to WDIO specs._
11
27
  * [wdio/ado-reporter](src-wdio_ado-reporter.md) - _**WDIO Reporter** to handle Azure DevOps Test Run generation._
12
28
 
13
- * ![category:internal](https://img.shields.io/badge/category-internal-009663.svg?style=flat-square)
14
- * [ado-client](src_ado-client.md) - _Azure DevOps rest client to push test results._
15
- * [logger](src_logger.md) - _Internal logger._
16
- * [page-objects/guerrillamail.page](src-page-objects_guerrillamail.page.md) - _Page-object on top of Guerilla Mail._
17
- * [page-objects/maildrop.page](src-page-objects_maildrop.page.md) - _Page-object on top of maildrop._
18
- * [page-objects/yopmail.page](src-page-objects_yopmail.page.md) - _Page-object on top of YOPmail._
19
-
20
29
  * ![category:other](https://img.shields.io/badge/category-other-9f9f9f.svg?style=flat-square)
21
30
  * [wdio/test-context-worker-service](src-wdio_test-context-worker-service.md) - _Module wdio/test-context-worker-service_
22
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionlogementservices/wdio-test-context",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A framework to add test context to webdriverio end to end tests.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -44,13 +44,19 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@faker-js/faker": "^9.6.0",
47
+ "amqplib": "^0.10.7",
48
+ "axios": "^1.8.4",
47
49
  "azure-devops-node-api": "^14.1.0",
48
50
  "chalk": "^5.4.1",
51
+ "dotenv-flow": "^4.1.0",
49
52
  "glob": "^11.0.1",
50
53
  "jspdf": "^3.0.1",
51
54
  "lodash.merge": "^4.6.2",
52
55
  "moment": "^2.30.1",
53
- "nodemailer": "^6.10.0"
56
+ "msnodesqlv8": "^4.5.0",
57
+ "mssql": "^11.0.1",
58
+ "nodemailer": "^6.10.0",
59
+ "syswide-cas": "^5.3.0"
54
60
  },
55
61
  "peerDependencies": {
56
62
  "@wdio/globals": "^9.12.1"
@@ -0,0 +1,49 @@
1
+ /**
2
+ * **Amqp client** to send message on a Amqp broker.
3
+ * Accessible through the **publishMessage** method of the TestContext instance.
4
+ * @module
5
+ * @category private
6
+ */
7
+
8
+ import amqp from 'amqplib';
9
+
10
+ import { logger } from './logger.js';
11
+
12
+ const appId = 'wdio-test';
13
+
14
+ /**
15
+ * Publishes the message to the specified exchange of the specified amqp server.
16
+ * @template T
17
+ * @param {string} connectionString connection string
18
+ * @param {string} exchange name of the exchange
19
+ * @param {string} routingKey routing key
20
+ * @param {string} type type of the message
21
+ * @param {T} payload message payload
22
+ * @param {string} correlationId correlation id for message tracking
23
+ * @returns {Promise<boolean>}
24
+ */
25
+ export async function publishMessage(connectionString, exchange, routingKey, type, payload, correlationId) {
26
+ let connection;
27
+ let channel;
28
+ try {
29
+ connection = await amqp.connect(connectionString, { clientProperties: { connection_name: appId } });
30
+ channel = await connection.createChannel();
31
+ const content = Buffer.from(JSON.stringify(payload));
32
+ /** @type {import('amqplib').Options.Publish} */ const options = {
33
+ appId,
34
+ contentType: 'application/json',
35
+ correlationId,
36
+ timestamp: Date.now(),
37
+ type
38
+ };
39
+ const result = channel.publish(exchange, routingKey, content, options);
40
+ if (result) logger.debug('Amqp message sent.');
41
+ return result;
42
+ } catch (error) {
43
+ logger.detailError(error);
44
+ throw new Error(`Amqp error: '${error.message}'!`);
45
+ } finally {
46
+ if (channel) channel.close();
47
+ if (connection) connection.close();
48
+ }
49
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * CA certificates loader.
3
+ * @module
4
+ * @category internal
5
+ */
6
+
7
+ import path from 'node:path';
8
+
9
+ import { glob } from 'glob';
10
+ import { addCAs } from 'syswide-cas';
11
+
12
+ /**
13
+ * Loads CA certificats if there are present in a ca folder.
14
+ */
15
+ export function loadCACertificates() {
16
+ const caFiles = glob.sync('**/*.crt', {
17
+ absolute: true,
18
+ cwd: path.join(process.cwd(), 'ca'),
19
+ nodir: true
20
+ });
21
+ if (caFiles && caFiles.length > 0) {
22
+ addCAs(caFiles);
23
+ }
24
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * **Sql client** to execute query on Miscrosoft SQL Server.
3
+ * Accessible through the **executeSql** method of the TestContext instance.
4
+ * @module
5
+ * @category private
6
+ */
7
+
8
+ import { logger } from './logger.js';
9
+
10
+ // import sql from 'mssql'
11
+
12
+ /**
13
+ * Executes the provided function passing the sql request object.
14
+ * @template T
15
+ * @param {string} connectionString sql server connection string
16
+ * @param {(sql: import('mssql').Request) => T} requestFunction function using the sql request object
17
+ * @returns {Promise<T>} sql query result
18
+ */
19
+ export async function executeSql(connectionString, requestFunction) {
20
+ const isOdbcDriver = connectionString.includes('Driver=');
21
+ const sqlConfiguration = isOdbcDriver ? { driver: 'msnodesqlv8', connectionString } : connectionString;
22
+ const sqlPackageName = isOdbcDriver ? 'mssql/msnodesqlv8.js' : 'mssql';
23
+ const { default: sql } = await import(sqlPackageName);
24
+ const pool = new sql.ConnectionPool(sqlConfiguration);
25
+ try {
26
+ const client = await pool.connect();
27
+ const result = await requestFunction(client.request());
28
+ logger.debug(`Sql query executed.`);
29
+ return result;
30
+ } catch (error) {
31
+ logger.detailError(error);
32
+ throw new Error(`Sql error: '${error.message}'!`);
33
+ }
34
+ }
@@ -7,18 +7,23 @@
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
 
10
+ import dotenvFlow from 'dotenv-flow';
10
11
  import { faker } from '@faker-js/faker/locale/fr';
11
12
  import merge from 'lodash.merge';
12
13
  import { SevereServiceError } from 'webdriverio';
13
14
 
14
15
  import { MailService } from './mail-service.js';
15
16
  import { logger } from './logger.js';
17
+ import { executeSql } from './sql-client.js';
18
+ import { publishMessage } from './amqp-client.js';
19
+ import { loadCACertificates } from './ca-loader.js';
20
+ import { runVtomJob } from './vtom-client.js';
16
21
 
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
+ /** @typedef {import('./types.d.ts').TestEnvironmentConfiguration} TestEnvironmentConfiguration */
23
+ /** @typedef {import('./types.d.ts').TestUser} TestUser */
24
+ /** @typedef {import('./types.d.ts').MailProviderName} MailProviderName */
25
+ /** @typedef {import('./types.d.ts').DataGenerator} DataGenerator */
26
+ /** @typedef {import('./types.d.ts').LogLevel} LogLevel */
22
27
 
23
28
  export const defaultDatasetName = 'default';
24
29
  const defaultRecordedUsersFilename = 'users.json';
@@ -44,7 +49,7 @@ export class TestContext {
44
49
  * Implements a test context with environments and dataset in webdriverio.
45
50
  * @param {string} name name of the environment (defined by TARGET_ENV environment variable)
46
51
  * @param {Record<string, any>} parameters custom parameters (like 'url')
47
- * @param {import('./types.js').EnvironmentOptions} [environmentOptions] custom options
52
+ * @param {import('./types.d.ts').EnvironmentOptions} [environmentOptions] custom options (like sql or rabbitMQ clients)
48
53
  * @returns {TestContext}
49
54
  */
50
55
  setEnvironment(name, parameters, environmentOptions) {
@@ -98,6 +103,11 @@ export class TestContext {
98
103
  * @returns {TestContext}
99
104
  */
100
105
  initialize() {
106
+ loadCACertificates();
107
+ // dotenv-flow configuration
108
+ dotenvFlow.config({
109
+ node_env: process.env.TARGET_ENV || this.defaultEnvironmentName
110
+ });
101
111
  // email provider
102
112
  this.mailService = new MailService(this._mailProviderName ?? defaultMailProviderName);
103
113
  // data folder
@@ -143,14 +153,22 @@ export class TestContext {
143
153
  }
144
154
 
145
155
  /**
146
- * Retrieves the name of the current environment.
156
+ * Retrieves the default (i.e. the first) environment name.
147
157
  * @returns {string}
148
158
  */
149
- get environmentName() {
159
+ get defaultEnvironmentName() {
150
160
  if (this._environments.size === 0)
151
161
  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();
162
+ return this._environments.keys().next().value;
163
+ }
164
+
165
+ /**
166
+ * Retrieves the name of the current environment.
167
+ * @returns {string}
168
+ */
169
+ get environmentName() {
170
+ const defaultEnvironmentName = this.defaultEnvironmentName;
171
+ return (process.env.TARGET_ENV || defaultEnvironmentName).toLowerCase();
154
172
  }
155
173
 
156
174
  /**
@@ -245,4 +263,44 @@ export class TestContext {
245
263
  users.push({ environment, dataset, date, lastname, firstname, gender, email, password });
246
264
  fs.writeFileSync(this._recordedUsersFilepath, JSON.stringify(users));
247
265
  }
266
+
267
+ /**
268
+ * Published the amqp message to the specified exchange.
269
+ * @template T
270
+ * @param {string} parameterName name of the environment parameter that defines the amqp connection string
271
+ * @param {string} exchange name of the exchange
272
+ * @param {string} routingKey routing key
273
+ * @param {string} type type of the message
274
+ * @param {T} payload message payload
275
+ * @param {string} correlationId correlation id for message tracking
276
+ * @returns {Promise<boolean>}
277
+ */
278
+ async publishMessage(parameterName, exchange, routingKey, type, payload, correlationId) {
279
+ const connectionString = this.getParameter(parameterName, true);
280
+ return publishMessage(connectionString, exchange, routingKey, type, payload, correlationId);
281
+ }
282
+
283
+ /**
284
+ * Executes the specified sql operation.
285
+ * @template T
286
+ * @param {string} parameterName name of the environment parameter that defines the sql connection string
287
+ * @param {(sql: import('mssql').Request) => T} sqlOperation function using the sql request object
288
+ * @returns {Promise<T>} sql query result
289
+ */
290
+ async executeSql(parameterName, sqlOperation) {
291
+ const connectionString = this.getParameter(parameterName, true);
292
+ return executeSql(connectionString, sqlOperation);
293
+ }
294
+
295
+ /**
296
+ * Runs the specified vtom job.
297
+ * @param {string} parameterName name of the environment parameter that defines the vtom configuration
298
+ * @param {string} appName vtom application name
299
+ * @param {string} jobName vtom job name
300
+ * @returns {Promise<boolean>} true if successful execution
301
+ */
302
+ async runVtomJob(parameterName, appName, jobName) {
303
+ const { url, apiKey, environment } = this.getParameter(parameterName, true);
304
+ return runVtomJob(url, apiKey, environment, appName, jobName);
305
+ }
248
306
  }
package/src/types.d.ts CHANGED
@@ -48,7 +48,7 @@ export type TestEnvironmentConfiguration = {
48
48
  /** options of the environment */ options: EnvironmentOptions;
49
49
  };
50
50
 
51
- /** Custom options for the environement */
51
+ /** Custom options for the environment */
52
52
  export type EnvironmentOptions = {
53
53
  /** Retrieve dataset **per environment** rather than globally. Dataset are global if not specified. */
54
54
  perEnvironmentData?: boolean;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * **Vtom client** to run Vtom job.
3
+ * Accessible through the **runVtomJob** method of the TestContext instance.
4
+ * @module
5
+ * @category private
6
+ */
7
+
8
+ import https from 'node:https';
9
+ import { constants } from 'node:crypto';
10
+
11
+ import axios from 'axios';
12
+
13
+ import { logger } from './logger.js';
14
+
15
+ /** @typedef {(environment: string, appName: string, jobName: string) => Promise<import('axios').AxiosResponse>} VtomApiCall */
16
+
17
+ /** @param {number} ms number of milliseconds to wait */
18
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
19
+
20
+ /**
21
+ * Creates the http client.
22
+ * @param {string} baseURL api base url
23
+ * @param {string} apiKey vtom api key
24
+ * @returns {import('axios').AxiosInstance}
25
+ */
26
+ function createClient(baseURL, apiKey) {
27
+ const httpsAgent = new https.Agent({
28
+ rejectUnauthorized: false,
29
+ secureOptions: constants.SSL_OP_LEGACY_SERVER_CONNECT
30
+ });
31
+ return axios.create({
32
+ httpsAgent,
33
+ headers: {
34
+ 'X-API-KEY': apiKey,
35
+ 'Content-Type': 'application/json'
36
+ },
37
+ baseURL
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Runs the specified vtom job.
43
+ * @param {string} apiUrl vtom base api url
44
+ * @param {string} apiKey vtom api key
45
+ * @param {string} environment vtom environment
46
+ * @param {string} appName vtom application name
47
+ * @param {string} jobName vtom job name
48
+ * @returns {Promise<boolean>}
49
+ */
50
+ export async function runVtomJob(apiUrl, apiKey, environment, appName, jobName) {
51
+ const client = createClient(apiUrl, apiKey);
52
+ try {
53
+ // stop job
54
+ logger.debug(`Stopping vtom job '${jobName}'...`);
55
+ await doAction(client, 'ChangeStatus', 'Finished')(environment, appName, jobName);
56
+ // run job
57
+ logger.debug(`Running vtom job '${jobName}'...`);
58
+ await doAction(client, 'ChangeStatus', 'Running')(environment, appName, jobName);
59
+ let jobStatus;
60
+ let maxCount = 20;
61
+ // check status every 5s
62
+ do {
63
+ const { data } = await getJobStatus(client)(environment, appName, jobName);
64
+ jobStatus = data?.status;
65
+ if (jobStatus === 'Finished') {
66
+ logger.info(`Vtom job '${jobName}' finished.`);
67
+ return true;
68
+ }
69
+ await wait(5000);
70
+ logger.debug(`Checking vtom job status (${maxCount}=${jobStatus})...`);
71
+ maxCount--;
72
+ } while (jobStatus !== 'Finished' && maxCount > 0);
73
+ return false;
74
+ } catch (error) {
75
+ logger.detailError(error);
76
+ throw new Error(`Vtom error: '${error.message}'!`);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Calls the vtom job action endpoint.
82
+ * @param {import('axios').AxiosInstance} client
83
+ * @param {'ChangeStatus'} type
84
+ * @param {'Running' | 'Finished'} status
85
+ * @returns {VtomApiCall}
86
+ */
87
+ const doAction = (client, type, status) => (environment, appName, jobName) =>
88
+ client.post(
89
+ `/${environment}/applications/${appName}/jobs/${jobName}/action`,
90
+ JSON.stringify({ comment: `wdio-test: ${type}('${status}')`, type, status })
91
+ );
92
+
93
+ /**
94
+ * Calls the vtom job status endpoint.
95
+ * @param {import('axios').AxiosInstance} client
96
+ * @returns {VtomApiCall}
97
+ */
98
+ const getJobStatus = client => (environment, appName, jobName) =>
99
+ client.get(`/${environment}/applications/${appName}/jobs/${jobName}/status`);
@@ -46,7 +46,8 @@ export default class TestContextWorkerService {
46
46
  * @param {string[]} _specs
47
47
  * @param {string} _cid
48
48
  */
49
- beforeSession(_config, _capabilities, _specs, _cid) {
49
+ async beforeSession(_config, _capabilities, _specs, _cid) {
50
+ logger.info(`Using '${this._context.environmentName}' test environement.`);
50
51
  logger.debug(
51
52
  inspect(filterPrivateProperty(this._context), {
52
53
  colors: true,