@actionlogementservices/wdio-test-context 2.0.0 → 2.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.
@@ -0,0 +1,2 @@
1
+ # Every change requires approval from the PR reviewers team
2
+ * @actionlogementservices/pr-reviewers
@@ -0,0 +1,92 @@
1
+ # Stages the package on npm when a merged PR brings a new version in package.json.
2
+ # npm authentication relies on npm trusted publishing (OIDC): no npm token is stored in GitHub.
3
+ # The staged version goes public only once a maintainer approves it on npmjs.com with 2FA,
4
+ # then the draft GitHub release has to be published to create the git tag.
5
+ name: Release
6
+
7
+ on:
8
+ push:
9
+ branches: [main]
10
+ workflow_dispatch:
11
+
12
+ # never run two releases at the same time
13
+ concurrency: release
14
+
15
+ permissions:
16
+ contents: write # creates the draft GitHub release
17
+ issues: write # creates the approval reminder issue
18
+ id-token: write # npm trusted publishing
19
+
20
+ jobs:
21
+ release:
22
+ runs-on: ubuntu-latest
23
+ env:
24
+ GH_TOKEN: ${{ github.token }}
25
+ steps:
26
+ - uses: actions/checkout@v5
27
+
28
+ - uses: actions/setup-node@v5
29
+ with:
30
+ node-version: 22
31
+ registry-url: https://registry.npmjs.org
32
+ cache: npm
33
+
34
+ # staged publishing requires npm >= 11.15.0
35
+ - run: npm install --global npm@^11.15.0
36
+
37
+ - name: Check whether the version is already published or staged
38
+ id: version
39
+ run: |
40
+ NAME=$(node -p "require('./package.json').name")
41
+ VERSION=$(node -p "require('./package.json').version")
42
+ PUBLISHED=$(npm view "$NAME@$VERSION" version 2>/dev/null || true)
43
+ echo "name=$NAME" >> "$GITHUB_OUTPUT"
44
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
45
+ if [ "$PUBLISHED" = "$VERSION" ]; then
46
+ echo "publish=false" >> "$GITHUB_OUTPUT"
47
+ echo "::notice::$NAME@$VERSION is already published, nothing to release."
48
+ # a (draft) GitHub release means the version has already been staged and awaits approval
49
+ elif gh release list --limit 100 --json tagName --jq '.[].tagName' | grep -Fxq "$VERSION"; then
50
+ echo "publish=false" >> "$GITHUB_OUTPUT"
51
+ echo "::notice::$NAME@$VERSION is already staged, approve it on npmjs.com."
52
+ else
53
+ echo "publish=true" >> "$GITHUB_OUTPUT"
54
+ fi
55
+ # prereleases (e.g. 5.1.0-0) go to the 'next' dist-tag, like release-it does
56
+ if [[ "$VERSION" == *-* ]]; then
57
+ echo "dist-tag=next" >> "$GITHUB_OUTPUT"
58
+ echo "prerelease=--prerelease" >> "$GITHUB_OUTPUT"
59
+ else
60
+ echo "dist-tag=latest" >> "$GITHUB_OUTPUT"
61
+ echo "prerelease=" >> "$GITHUB_OUTPUT"
62
+ fi
63
+
64
+ # no build step: install scripts are skipped (msnodesqlv8 native addon is not needed to lint)
65
+ - if: steps.version.outputs.publish == 'true'
66
+ run: npm ci --ignore-scripts
67
+
68
+ - if: steps.version.outputs.publish == 'true'
69
+ run: npm test
70
+
71
+ - if: steps.version.outputs.publish == 'true'
72
+ run: npm stage publish --tag ${{ steps.version.outputs.dist-tag }}
73
+
74
+ # a draft release creates no git tag: the tag is created when the draft is published
75
+ - name: Create the draft GitHub release and the approval reminder
76
+ if: steps.version.outputs.publish == 'true'
77
+ env:
78
+ NAME: ${{ steps.version.outputs.name }}
79
+ VERSION: ${{ steps.version.outputs.version }}
80
+ run: |
81
+ RELEASE_URL=$(gh release create "$VERSION" --draft --target "$GITHUB_SHA" --title "$VERSION" --generate-notes ${{ steps.version.outputs.prerelease }})
82
+ BODY="$NAME@$VERSION has been staged on npm and awaits your approval.
83
+
84
+ - [ ] Approve it in the **Staged Packages** tab of https://www.npmjs.com/package/$NAME (or \`npm stage list\` / \`npm stage approve <stage-id>\`)
85
+ - [ ] Publish the draft GitHub release, which creates the \`$VERSION\` git tag: $RELEASE_URL
86
+ - [ ] Close this issue
87
+
88
+ If you reject the staged version, delete the draft release and close this issue."
89
+ echo "$BODY" >> "$GITHUB_STEP_SUMMARY"
90
+ # the reminder issue notifies the person who merged (requires issues to be enabled on the repository)
91
+ gh issue create --assignee "$GITHUB_ACTOR" --title "Approve the npm release $VERSION" --body "$BODY" \
92
+ || echo "::warning::Could not create the reminder issue (are issues enabled on the repository?)"
package/README.md CHANGED
@@ -1,12 +1,21 @@
1
1
  # wdio-test-context
2
2
 
3
+ [![npm package](https://img.shields.io/npm/v/%40actionlogementservices%2Fwdio-test-context)](https://www.npmjs.com/package/@actionlogementservices/wdio-test-context)
4
+
3
5
  A framework to add test context to webdriverio end to end tests.
4
6
 
5
- - It uses JSON files (named dataset) and optional javascript function to add dynamic content to your dataset.
6
- - It consumes a disposable mail service (YOPmail, maildrop or Guerilla Mail) in order to check for new message.
7
+ - It uses JSON files (named datasets) and an optional javascript function to add dynamic content to your dataset.
8
+ - It generates a random test user (name, gender, disposable email address) for each run.
9
+ - It consumes a disposable mail service (maildrop, YOPmail or Guerrilla Mail) in order to check for new messages.
10
+ - It executes SQL queries on Microsoft SQL Server, publishes AMQP (RabbitMQ) messages and runs VTOM jobs.
7
11
  - It provides pdf generation to enable document uploads.
8
12
  - It provides an Azure DevOps webdriverio reporter.
9
- - It generates a screenshot when the test fails.
13
+ - It generates a screenshot when a test fails.
14
+
15
+ ## Prerequisites
16
+
17
+ - Node.js 22 or later.
18
+ - webdriverio 9 (`@wdio/globals` is a peer dependency).
10
19
 
11
20
  ## Installation
12
21
 
@@ -16,11 +25,24 @@ A framework to add test context to webdriverio end to end tests.
16
25
  npm install @actionlogementservices/wdio-test-context
17
26
  ```
18
27
 
19
- - Create a `data` folder
28
+ - Create a `data` folder.
29
+
30
+ - Create JSON files in the `data` folder: these are your datasets. The content of the selected dataset is merged into the test context. For instance `data/test.json`:
20
31
 
21
- - Create JSON files in the `data` folder: this will be your datasets.
32
+ ```json
33
+ {
34
+ "company": { "name": "ACME" }
35
+ }
36
+ ```
22
37
 
23
- - Create a `data/test-context.d.ts`: this will add intellisense when you use your test context data within the spec files.
38
+ - Create a `data/test-context.d.ts` file: it adds intellisense when you use your test context data within the spec files.
39
+
40
+ ```ts
41
+ export type TestContextModel = {
42
+ company: { name: string };
43
+ customDate: Date;
44
+ };
45
+ ```
24
46
 
25
47
  - Create a `data/test-context.js` file with the following code:
26
48
 
@@ -35,10 +57,12 @@ A framework to add test context to webdriverio end to end tests.
35
57
  /** @type {TestContextModel & TestContext} */
36
58
  export const testContext = /** @type {TestContextModel & TestContext} */ (
37
59
  new TestContext()
38
- .setDataGenerator(generateRandomData) // optional : add your own dynamic data
39
- .setDefaultDataset('test') // default dataset name when DATASET env var is not specified
60
+ .setDataGenerator(generateRandomData) // add your own dynamic data
61
+ .setDefaultDataset('test') // default dataset name when the DATASET env var is not specified
62
+ .setMailProvider('maildrop') // optional: 'maildrop' (default), 'yopmail' or 'guerrillamail'
63
+ .setSmtpServer({ host: 'smtp.domain.com', port: 25 }) // optional: only to send emails with mailService.sendMessage
40
64
  .setEnvironment('local', {
41
- //define parameters for your environment
65
+ // define the parameters of your environment
42
66
  frontUrl: 'https://test.lvh.me',
43
67
  backOfficeUrl: 'https://test-backoffice.lvh.me:4432',
44
68
  sso: true
@@ -51,17 +75,22 @@ A framework to add test context to webdriverio end to end tests.
51
75
  );
52
76
  ```
53
77
 
54
- - Create optionnally `data/generate-random-data.js` to add dynamic data, file with the following code:
78
+ > **NOTES**
79
+ >
80
+ > - The first environment is the default one when the `TARGET_ENV` env var is not specified.
81
+ > - `setEnvironment` accepts a third `options` argument. With `{ perEnvironmentData: true }`, the datasets of this environment are read from `data/{environment_name}/` instead of `data/`.
82
+ > - The mail provider can be overridden with the `MAILPROVIDER` env var.
83
+
84
+ - Create `data/generate-random-data.js` to add dynamic data, with the following code:
55
85
 
56
86
  ```js
57
87
  import { faker } from '@faker-js/faker/locale/fr';
58
- import moment from 'moment';
59
88
 
60
89
  /** @typedef {import('@actionlogementservices/wdio-test-context/types').DataGenerator} DataGenerator */
61
90
 
62
91
  /** @type {DataGenerator} */
63
92
  function generateRandomData(_context) {
64
- // dates random
93
+ // random date
65
94
  const customDate = faker.date.between({ from: '1995-01-01', to: '2001-01-01' });
66
95
  return {
67
96
  customDate
@@ -71,32 +100,7 @@ A framework to add test context to webdriverio end to end tests.
71
100
  export { generateRandomData };
72
101
  ```
73
102
 
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
-
99
- - Configure webdriverio: in your wdio.conf.js:
103
+ - Configure webdriverio in your `wdio.conf.js`:
100
104
 
101
105
  ```js
102
106
  import { AdoReporter, TestContextWorkerService } from '@actionlogementservices/wdio-test-context';
@@ -110,168 +114,225 @@ A framework to add test context to webdriverio end to end tests.
110
114
  }
111
115
  ```
112
116
 
117
+ The `logLevel` option accepts `none`, `error` (default), `warn`, `info` or `debug`.
118
+
119
+ ## Environment variables
120
+
121
+ | Name | Description |
122
+ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
123
+ | `USER_PASSWORD` | **Required.** Password given to the generated test user (`testContext.user.password`). |
124
+ | `TARGET_ENV` | Name of the environment to use. Defaults to the first environment set. |
125
+ | `DATASET` | Name of the dataset (JSON file name without extension). Defaults to `setDefaultDataset` value. |
126
+ | `MAILPROVIDER` | Overrides the disposable mail provider (`maildrop`, `yopmail` or `guerrillamail`). |
127
+ | `EMAIL` | Reuses a previously recorded user (see [Test user](#test-user)) instead of generating a new one. |
128
+ | `DEBUG` | When set, raises the `BasePage` default wait timeout from 20 s to 1 hour, for step by step debugging. |
129
+ | `SYSTEM_ACCESSTOKEN` or `TOKEN` | Azure DevOps reporter: pipeline access token, or personal access token when `TOKEN` is used. |
130
+ | `SYSTEM_TEAMFOUNDATIONCOLLECTIONURI`, `SYSTEM_TEAMPROJECT`, `BUILD_BUILDID` | Azure DevOps reporter: set automatically by Azure Pipelines. |
131
+
113
132
  ## Notes on secrets
114
133
 
115
- If you have **secrets** in your parameter values, you need to
134
+ Environment variables are loaded with [dotenv-flow](https://www.npmjs.com/package/dotenv-flow), the environment name (`TARGET_ENV`) being the dotenv-flow `node_env`: `.env`, `.env.local`, `.env.{environment_name}` and `.env.{environment_name}.local` files are read.
116
135
 
117
- - create a `.env.{your_environment_name}.local` file (for instance `.env.staging.local`) with your secret configuration parameters:
136
+ If you have **secrets** in your parameter values, you need to:
137
+
138
+ - create a `.env.{environment_name}.local` file (for instance `.env.staging.local`) with your secret configuration parameters:
118
139
 
119
140
  ```sh
120
- DB_PWD="MySyperStrongPassword"
121
- CLIENT_SECRET="MySuperSafeSecret"
141
+ USER_PASSWORD="MySuperStrongPassword"
142
+ DB_PWD="MySuperStrongPassword"
143
+ RMQ_USER="MyUser"
144
+ RMQ_PWD="MySuperSafeSecret"
122
145
  ```
123
146
 
124
- - references the secret in the environment.js like this:
147
+ - reference the secret in `data/test-context.js` with a **template literal** (backticks, not quotes):
125
148
 
126
149
  ```js
127
150
  .setEnvironment('staging', {
128
- sql: 'Server=YourServer;Password=${process.env.DB_PWD};...'
151
+ sqlBO: `Server=YourServer;Database=YourDatabase;User Id=YourUser;Password=${process.env.DB_PWD};`
152
+ })
129
153
  ```
130
154
 
131
- - to add the following entries to your `.gitignore` to prevent storing secret values in your repository:
155
+ - add the following entries to your `.gitignore` to prevent storing secret values in your repository:
132
156
 
133
157
  ```sh
134
158
  .env.local
135
159
  .env.*.local
160
+ # recorded test users, including their password
161
+ data/log/
162
+ # error screenshots
163
+ screenshot/
136
164
  ```
137
165
 
138
166
  ## Use the test context in the spec file
139
167
 
168
+ ### Test user
169
+
170
+ - A random user is generated at each run and is available in `testContext.user` (`gender`, `firstname`, `lastname`, `email` on a domain of the mail provider, `password` from the `USER_PASSWORD` env var).
171
+ - Call `testContext.recordTestUser()` (for instance once the user account is created) to append the user to `data/log/users.json`. As it contains the passwords, a `.gitignore` is created in `data/log/` so that the folder is never committed.
172
+ - Run the tests with `EMAIL=<recorded email>` to reuse a recorded user of the current environment, or `EMAIL=last` to reuse the last recorded one.
173
+
140
174
  ### To access the mail service
141
175
 
142
- - Access the `mailService` property:
176
+ - Use the `mailService` property:
143
177
 
144
178
  ```js
145
- import { testContext } from '../../environments.js';
179
+ import { testContext } from '../data/test-context.js';
146
180
 
147
- describe(`[Inbox] - ${testContext.user.email} - I`, () => {
148
- let confirmationLink;
149
- it('receive a mail with confirmation code within 10s', async () => {
150
- await browser.open(testContext.getParameter('frontUrl')); // to retrieve parameters
181
+ describe(`[Inbox] - ${testContext.user.email}`, () => {
182
+ it('receives a mail with a confirmation code', async () => {
183
+ await browser.url(testContext.getParameter('frontUrl')); // to retrieve parameters
184
+ ...
151
185
  const from = 'no-reply@domain.com';
152
- const subject = `Confirmation code`;
153
- // to wait for email and to access test context data
186
+ const subject = 'Confirmation code';
187
+ // to wait for the email and to access the test context data
154
188
  const content = await testContext.mailService.waitForMessage(testContext.user.email, from, subject);
155
- confirmationCode = getConfirmationCode(content);
189
+ const confirmationCode = getConfirmationCode(content); // your own function
156
190
  expect(confirmationCode).toContain('123456');
157
191
  });
158
- }
192
+ });
159
193
  ```
160
194
 
195
+ - To send an email, define the smtp server with `setSmtpServer({ host, port, secure })` in `data/test-context.js`, then call `await testContext.mailService.sendMessage(from, to, subject, html)`.
196
+
161
197
  ### To execute SQL queries
162
198
 
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`.
199
+ - You need to define a **parameter** in `data/test-context.js` that contains the **sql connection string**. In the following example it is named `sqlBO`.
164
200
 
165
201
  ```js
166
202
  export const testContext = /** @type {TestContextModel & TestContext} */ (
167
203
  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
- )
204
+ .setEnvironment('local', {
205
+ frontUrl: 'https://test.lvh.me',
206
+ sqlBO: String.raw`Driver={ODBC Driver 17 for SQL Server};Server=(localdb)\MSSQLLocalDB;Database=YourDatabase;Trusted_Connection=yes;`
207
+ })
208
+ .initialize()
209
+ );
175
210
  ```
176
211
 
177
212
  > **NOTES on SQL connection string**
178
213
  >
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.
214
+ > - 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.
215
+ > - For a 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.
216
+ > - You **cannot** mix both syntaxes in the same environment.
217
+ > - In the previous example, `(localdb)` is used with the `ODBC Driver 17 for SQL Server`, which is installed.
183
218
 
184
- - Somewhere in you spec files
219
+ - Somewhere in your spec files:
185
220
 
186
221
  ```js
187
- import { testContext } from '../../environments.js';
222
+ import { testContext } from '../data/test-context.js';
188
223
 
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
- ...
224
+ describe('Before starting the test, we', () => {
225
+ it('check the sql version', async () => {
226
+ const result = await testContext.executeSql('sqlBO', sql => sql.query('SELECT @@Version'));
227
+ ...
228
+ });
194
229
  });
195
230
  ```
196
231
 
197
- ### To publish AMQP message
232
+ ### To publish AMQP messages
198
233
 
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`.
234
+ - You need to define a **parameter** in `data/test-context.js` that contains the **amqp connection string**. In the following example it is named `amqpBO`.
200
235
 
201
236
  ```js
202
237
  export const testContext = /** @type {TestContextModel & TestContext} */ (
203
238
  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
- )
239
+ .setEnvironment('local', {
240
+ frontUrl: 'https://test.lvh.me',
241
+ amqpBO: `amqps://${process.env.RMQ_USER}:${process.env.RMQ_PWD}@your-host:5671/your-vhost`
242
+ })
243
+ .initialize()
244
+ );
211
245
  ```
212
246
 
213
- - Somewhere in you spec files
247
+ - Somewhere in your spec files:
214
248
 
215
249
  ```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);
250
+ import { testContext } from '../data/test-context.js';
251
+
252
+ describe('Before starting the test, we', () => {
253
+ it('send an amqp message', async () => {
254
+ const result = await testContext.publishMessage(
255
+ 'amqpBO',
256
+ 'exchange', // your exchange name
257
+ 'routingKey', // your routing key
258
+ 'type', // your message type
259
+ { test: true }, // your message payload
260
+ testContext.user.email // correlation id to track your message
261
+ );
262
+ expect(result).toBe(true);
263
+ });
229
264
  });
230
265
  ```
231
266
 
232
267
  ### To run a VTOM job
233
268
 
234
- - You need to define a **parameter** in `your environment.js` that contains the **VTOM configuration**. In the following exemple it is named `vtomBO`.
269
+ - You need to define a **parameter** in `data/test-context.js` that contains the **VTOM configuration**. In the following example it is named `vtomBO`.
235
270
 
236
271
  ```js
237
272
  export const testContext = /** @type {TestContextModel & TestContext} */ (
238
273
  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
- },
274
+ .setEnvironment('local', {
275
+ frontUrl: 'https://test.lvh.me',
276
+ vtomBO: {
277
+ url: 'https://...',
278
+ apiKey: process.env.VTOM_API_KEY,
279
+ environment: 'DEV'
248
280
  }
249
- )
281
+ })
282
+ .initialize()
283
+ );
250
284
  ```
251
285
 
252
- - Somewhere in you spec files
286
+ - Somewhere in your spec files (the job is stopped, started, then its status is checked every 5 s for 100 s at most):
253
287
 
254
288
  ```js
255
- import { testContext } from '../../environments.js';
256
-
257
- describe(`Before starting the test, we`, () => {
289
+ import { testContext } from '../data/test-context.js';
258
290
 
259
- it('run a vtom job', async () => {
260
- const result = await testContext.runVtomJob('vtomBO', 'app', 'job');
261
- expect(result).toBe(true);
291
+ describe('Before starting the test, we', () => {
292
+ it('run a vtom job', async () => {
293
+ const result = await testContext.runVtomJob('vtomBO', 'app', 'job');
294
+ expect(result).toBe(true);
295
+ });
262
296
  });
263
297
  ```
264
298
 
265
- ## Run your test
299
+ ### Other helpers
300
+
301
+ - `BasePage`: base class for your page objects (tab management, wait helpers…).
302
+ - `pdfGenerator`: `generatePdf(filename, testContext)` creates a pdf file to upload and returns its path, `deletePdf(filepath)` deletes it.
303
+
304
+ ## Corporate CA certificates
305
+
306
+ If your environments use certificates issued by a private certificate authority, put the CA certificates (`*.crt` files) in a `ca` folder at the root of your test project: they are trusted when the test context is initialized.
266
307
 
267
- - Specify the dataset with the `DATASET` env var and the environment with the `TARGET_ENV` env var
308
+ ## Run your tests
309
+
310
+ - Specify the dataset with the `DATASET` env var and the environment with the `TARGET_ENV` env var:
268
311
 
269
312
  ```sh
270
313
  TARGET_ENV=staging DATASET=full-scenario npx wdio
271
- or
314
+ # or
272
315
  TARGET_ENV=staging DATASET=full-scenario npm test
273
316
  ```
274
317
 
318
+ - When a test fails, a screenshot and a text file with the error are saved in `screenshot/{environment_name}/{dataset}/`.
319
+
320
+ - With the `AdoReporter`, the results are published as an Azure DevOps test run at the end of the run. In an Azure Pipelines job, map the access token to the script: `env: { SYSTEM_ACCESSTOKEN: $(System.AccessToken) }`.
321
+
275
322
  ## Code documentation
276
323
 
277
- - Code is [documented here](./doc/toc.md).
324
+ - Code is [documented here](./doc/toc.md) (generated with `npm run doc`).
325
+
326
+ ## Release
327
+
328
+ The package is published on npm by the `Release` GitHub workflow, with npm trusted publishing (no npm token is stored in GitHub):
329
+
330
+ 1. In your pull request, bump the version with `npm run bump -- <major|minor|patch|prerelease>` (updates `package.json` and `package-lock.json` only).
331
+ 1. When the pull request is merged on `main`, the workflow runs the linter and **stages** the new version on npm.
332
+ Prerelease versions (e.g. `2.1.0-0`) are staged with the `next` dist-tag.
333
+ 1. The workflow creates a draft GitHub release and an issue assigned to the person who merged, as a reminder to:
334
+ - approve the staged version in the **Staged Packages** tab of the [npm package](https://www.npmjs.com/package/@actionlogementservices/wdio-test-context) (2FA required),
335
+ - publish the draft GitHub release, which creates the git tag,
336
+ - close the issue.
337
+
338
+ If the staged version is rejected, delete the draft release and close the issue.
@@ -82,6 +82,19 @@ __*return*__ | [TestContext](src_test-context.md) | **
82
82
 
83
83
  ---
84
84
 
85
+ ### `setSmtpServer(smtpServer) ► TestContext`
86
+
87
+ ![modifier: public](images/badges/modifier-public.png)
88
+
89
+ Defines the **smtp server** used by the mail service to send emails.
90
+
91
+ Parameters | Type | Description
92
+ --- | --- | ---
93
+ __smtpServer__ | `SmtpServer` | *smtp server*
94
+ __*return*__ | [TestContext](src_test-context.md) | **
95
+
96
+ ---
97
+
85
98
  ### `setLogLevel(level)`
86
99
 
87
100
  ![modifier: public](images/badges/modifier-public.png)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionlogementservices/wdio-test-context",
3
- "version": "2.0.0",
3
+ "version": "2.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",
@@ -12,7 +12,9 @@
12
12
  "scripts": {
13
13
  "prettier": "prettier \"./src/**/*.js\" --write",
14
14
  "eslint": "eslint \"./src/**/*.js\"",
15
- "doc": "jsdoc -c ./.jsdoc.json"
15
+ "doc": "jsdoc -c ./.jsdoc.json",
16
+ "test": "npm run eslint",
17
+ "bump": "npm version --no-git-tag-version"
16
18
  },
17
19
  "repository": {
18
20
  "type": "git",
@@ -44,6 +46,7 @@
44
46
  },
45
47
  "dependencies": {
46
48
  "@faker-js/faker": "^10.4.0",
49
+ "@wdio/reporter": "^9.27.1",
47
50
  "amqplib": "^2.0.1",
48
51
  "axios": "^1.16.1",
49
52
  "azure-devops-node-api": "^15.1.2",
@@ -59,7 +62,8 @@
59
62
  "syswide-cas": "^5.3.0"
60
63
  },
61
64
  "peerDependencies": {
62
- "@wdio/globals": "^9.27.1"
65
+ "@wdio/globals": "^9.27.1",
66
+ "webdriverio": "^9.27.1"
63
67
  },
64
68
  "engines": {
65
69
  "node": ">=22"
@@ -11,16 +11,20 @@ import * as provider from './page-objects/index.js';
11
11
  import { logger } from './logger.js';
12
12
 
13
13
  /** @typedef {import('./types.js').MailProvider} MailProvider */
14
+ /** @typedef {import('./types.js').SmtpServer} SmtpServer */
14
15
 
15
16
  export class MailService {
16
17
  /** disposable email provider @type {MailProvider} */ provider;
18
+ /** smtp server used to send emails @type {SmtpServer | undefined} */ smtpServer;
17
19
 
18
20
  /**
19
21
  * Implements a service that sends and retrieves emails with a given provider.
20
22
  * @param {import('./types.js').MailProviderName} providerName disposable email provider name
23
+ * @param {SmtpServer} [smtpServer] smtp server used to send emails
21
24
  */
22
- constructor(providerName) {
25
+ constructor(providerName, smtpServer) {
23
26
  this.provider = provider[providerName].default;
27
+ this.smtpServer = smtpServer;
24
28
  }
25
29
 
26
30
  /**
@@ -48,7 +52,10 @@ export class MailService {
48
52
  * @returns {Promise<void>}
49
53
  */
50
54
  async sendMessage(from, to, subject, html) {
51
- const transporter = nodemailer.createTransport({ host: 'smtp.als.lan', secure: false, port: 25 });
55
+ if (!this.smtpServer?.host)
56
+ throw new Error('No smtp server defined! Call setSmtpServer on the test context to send emails.');
57
+ const { host, port, secure } = this.smtpServer;
58
+ const transporter = nodemailer.createTransport({ host, port: port ?? 25, secure: secure ?? false });
52
59
  await transporter.sendMail({ from, to, subject, html });
53
60
  }
54
61
 
@@ -24,6 +24,7 @@ import { runVtomJob } from './vtom-client.js';
24
24
  /** @typedef {import('./types.d.ts').MailProviderName} MailProviderName */
25
25
  /** @typedef {import('./types.d.ts').DataGenerator} DataGenerator */
26
26
  /** @typedef {import('./types.d.ts').LogLevel} LogLevel */
27
+ /** @typedef {import('./types.d.ts').SmtpServer} SmtpServer */
27
28
 
28
29
  export const defaultDatasetName = 'default';
29
30
  const defaultRecordedUsersFilename = 'users.json';
@@ -39,6 +40,7 @@ export class TestContext {
39
40
  /** Test environments @private @type {Map<string, TestEnvironmentConfiguration>} */
40
41
  _environments = new Map();
41
42
  /** Mail provider name @private @type {MailProviderName} */ _mailProviderName;
43
+ /** Smtp server @private @type {SmtpServer} */ _smtpServer;
42
44
  /** data folder @private @type {string} */ _dataFolder;
43
45
  /** log folder @private @type {string} */ _logFolder;
44
46
  /** data folder @private @type {string} */ _recordedUsersFilepath;
@@ -90,6 +92,16 @@ export class TestContext {
90
92
  return this;
91
93
  }
92
94
 
95
+ /**
96
+ * Defines the **smtp server** used by the mail service to send emails.
97
+ * @param {SmtpServer} smtpServer smtp server
98
+ * @returns {TestContext}
99
+ */
100
+ setSmtpServer(smtpServer) {
101
+ this._smtpServer = smtpServer;
102
+ return this;
103
+ }
104
+
93
105
  /**
94
106
  * Sets the logging level.
95
107
  * @param {LogLevel} level logging level
@@ -109,7 +121,7 @@ export class TestContext {
109
121
  node_env: process.env.TARGET_ENV || this.defaultEnvironmentName
110
122
  });
111
123
  // email provider
112
- this.mailService = new MailService(this._mailProviderName ?? defaultMailProviderName);
124
+ this.mailService = new MailService(this._mailProviderName ?? defaultMailProviderName, this._smtpServer);
113
125
  // data folder
114
126
  this._dataFolder =
115
127
  (this.environment.options.perEnvironmentData ?? false)
@@ -123,7 +135,7 @@ export class TestContext {
123
135
  // generate random user
124
136
  const user = this._generateRandomUser(this.mailService);
125
137
  // generate custom data
126
- const customData = this._dataGenerator(this);
138
+ const customData = this._dataGenerator?.(this) ?? {};
127
139
  // load dataset
128
140
  const data = JSON.parse(fs.readFileSync(datasetFilepath, 'utf8'));
129
141
  // add dataset to test context
@@ -255,6 +267,9 @@ export class TestContext {
255
267
  fs.mkdirSync(this._logFolder, { recursive: true });
256
268
  fs.writeFileSync(this._recordedUsersFilepath, JSON.stringify([]));
257
269
  }
270
+ // recorded users contain passwords: never commit the log folder
271
+ const gitignoreFilepath = path.join(this._logFolder, '.gitignore');
272
+ if (!fs.existsSync(gitignoreFilepath)) fs.writeFileSync(gitignoreFilepath, '*\n');
258
273
  const { firstname, lastname, gender, email, password } = this.user;
259
274
  const environment = this.environmentName;
260
275
  const dataset = this.dataset;
package/src/types.d.ts CHANGED
@@ -23,6 +23,13 @@ export type MailProvider = {
23
23
  /** wdio logic to retieve email content */ getMessageContent: (index: number) => Promise<string>;
24
24
  };
25
25
 
26
+ /** Smtp server used to send emails */
27
+ export type SmtpServer = {
28
+ /** host name */ host: string;
29
+ /** port (25 by default) */ port?: number;
30
+ /** use TLS from the start of the connection (false by default) */ secure?: boolean;
31
+ };
32
+
26
33
  /** Randomly auto generated user */
27
34
  export type TestUser = {
28
35
  /** last name */ lastname: string;
@@ -76,7 +76,7 @@ export default class TestContextWorkerService {
76
76
  const errorLines = stack?.split('at ') ?? [];
77
77
  const errorFile = errorLines
78
78
  .find((/** @type {string} */ l) => l.toLowerCase().includes('e2e.js'))
79
- .trim();
79
+ ?.trim();
80
80
  const fileContent = `Test : ${test.fullName}\nErreur : ${message}${
81
81
  errorFile ? '\nEmplacement : ' + errorFile : ''
82
82
  }`;