codeceptjs 3.7.0-beta.9 → 3.7.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.
Files changed (50) hide show
  1. package/bin/codecept.js +1 -1
  2. package/lib/codecept.js +14 -12
  3. package/lib/command/check.js +33 -9
  4. package/lib/command/definitions.js +1 -1
  5. package/lib/command/gherkin/snippets.js +69 -69
  6. package/lib/command/interactive.js +1 -1
  7. package/lib/command/run-multiple/chunk.js +48 -45
  8. package/lib/container.js +14 -7
  9. package/lib/effects.js +7 -2
  10. package/lib/event.js +2 -0
  11. package/lib/helper/AI.js +2 -1
  12. package/lib/helper/Playwright.js +1 -1
  13. package/lib/helper/Puppeteer.js +1 -1
  14. package/lib/helper/extras/Popup.js +22 -22
  15. package/lib/mocha/asyncWrapper.js +3 -1
  16. package/lib/mocha/gherkin.js +1 -1
  17. package/lib/mocha/inject.js +5 -0
  18. package/lib/mocha/test.js +5 -2
  19. package/lib/plugin/analyze.js +50 -3
  20. package/lib/plugin/auth.js +435 -0
  21. package/lib/plugin/autoDelay.js +2 -2
  22. package/lib/plugin/autoLogin.js +3 -337
  23. package/lib/plugin/pageInfo.js +1 -1
  24. package/lib/plugin/retryFailedStep.js +13 -14
  25. package/lib/plugin/retryTo.js +6 -17
  26. package/lib/plugin/screenshotOnFail.js +4 -5
  27. package/lib/plugin/standardActingHelpers.js +4 -1
  28. package/lib/plugin/stepByStepReport.js +1 -1
  29. package/lib/plugin/tryTo.js +6 -15
  30. package/lib/recorder.js +1 -0
  31. package/lib/step/base.js +15 -4
  32. package/lib/step/comment.js +10 -0
  33. package/lib/store.js +29 -5
  34. package/lib/utils.js +1 -1
  35. package/lib/within.js +2 -0
  36. package/package.json +18 -18
  37. package/translations/de-DE.js +4 -3
  38. package/translations/fr-FR.js +4 -3
  39. package/translations/index.js +1 -0
  40. package/translations/it-IT.js +4 -3
  41. package/translations/ja-JP.js +4 -3
  42. package/translations/nl-NL.js +76 -0
  43. package/translations/pl-PL.js +4 -3
  44. package/translations/pt-BR.js +4 -3
  45. package/translations/ru-RU.js +4 -3
  46. package/translations/utils.js +9 -0
  47. package/translations/zh-CN.js +4 -3
  48. package/translations/zh-TW.js +4 -3
  49. package/typings/promiseBasedTypes.d.ts +0 -652
  50. package/typings/types.d.ts +99 -655
@@ -2,66 +2,66 @@
2
2
  * Class to handle the interaction with the Dialog (Popup) Class from Puppeteer
3
3
  */
4
4
  class Popup {
5
- constructor(popup, defaultAction) {
6
- this._popup = popup || null;
7
- this._actionType = '';
8
- this._defaultAction = defaultAction || '';
5
+ constructor(popup = null, defaultAction = '') {
6
+ this._popup = popup
7
+ this._actionType = ''
8
+ this._defaultAction = defaultAction
9
9
  }
10
10
 
11
11
  _assertValidActionType(action) {
12
12
  if (['accept', 'cancel'].indexOf(action) === -1) {
13
- throw new Error('Invalid Popup action type. Only "accept" or "cancel" actions are accepted');
13
+ throw new Error('Invalid Popup action type. Only "accept" or "cancel" actions are accepted')
14
14
  }
15
15
  }
16
16
 
17
17
  set defaultAction(action) {
18
- this._assertValidActionType(action);
19
- this._defaultAction = action;
18
+ this._assertValidActionType(action)
19
+ this._defaultAction = action
20
20
  }
21
21
 
22
22
  get defaultAction() {
23
- return this._defaultAction;
23
+ return this._defaultAction
24
24
  }
25
25
 
26
26
  get popup() {
27
- return this._popup;
27
+ return this._popup
28
28
  }
29
29
 
30
30
  set popup(popup) {
31
31
  if (this._popup) {
32
- console.error('Popup already exists and was not closed. Popups must always be closed by calling either I.acceptPopup() or I.cancelPopup()');
32
+ return
33
33
  }
34
- this._popup = popup;
34
+ this._popup = popup
35
35
  }
36
36
 
37
37
  get actionType() {
38
- return this._actionType;
38
+ return this._actionType
39
39
  }
40
40
 
41
41
  set actionType(action) {
42
- this._assertValidActionType(action);
43
- this._actionType = action;
42
+ this._assertValidActionType(action)
43
+ this._actionType = action
44
44
  }
45
45
 
46
46
  clear() {
47
- this._popup = null;
48
- this._actionType = '';
47
+ this._popup = null
48
+ this._actionType = ''
49
49
  }
50
50
 
51
51
  assertPopupVisible() {
52
52
  if (!this._popup) {
53
- throw new Error('There is no Popup visible');
53
+ throw new Error('There is no Popup visible')
54
54
  }
55
55
  }
56
56
 
57
57
  assertPopupActionType(type) {
58
- this.assertPopupVisible();
59
- const expectedAction = this._actionType || this._defaultAction;
58
+ this.assertPopupVisible()
59
+ const expectedAction = this._actionType || this._defaultAction
60
60
  if (expectedAction !== type) {
61
- throw new Error(`Popup action does not fit the expected action type. Expected popup action to be '${expectedAction}' not '${type}`);
61
+ throw new Error(`Popup action does not fit the expected action type. Expected popup action to be '${expectedAction}' not '${type}`)
62
62
  }
63
- this.clear();
63
+ this.clear()
64
64
  }
65
65
  }
66
66
 
67
- module.exports = Popup;
67
+ module.exports = Popup
@@ -145,11 +145,13 @@ module.exports.injected = function (fn, suite, hookName) {
145
145
  const opts = suite.opts || {}
146
146
  const retries = opts[`retry${ucfirst(hookName)}`] || 0
147
147
 
148
+ const currentTest = hookName === 'before' || hookName === 'after' ? suite?.ctx?.currentTest : null
149
+
148
150
  promiseRetry(
149
151
  async (retry, number) => {
150
152
  try {
151
153
  recorder.startUnlessRunning()
152
- await fn.call(this, getInjectedArguments(fn))
154
+ await fn.call(this, { ...getInjectedArguments(fn), suite, test: currentTest })
153
155
  await recorder.promise().catch(err => retry(err))
154
156
  } catch (err) {
155
157
  retry(err)
@@ -107,7 +107,7 @@ module.exports = (text, file) => {
107
107
  )
108
108
  continue
109
109
  }
110
- if (child.scenario && (currentLanguage ? child.scenario.keyword === currentLanguage.contexts.ScenarioOutline : child.scenario.keyword === 'Scenario Outline')) {
110
+ if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline.includes(child.scenario.keyword) : child.scenario.keyword === 'Scenario Outline')) {
111
111
  for (const examples of child.scenario.examples) {
112
112
  const fields = examples.tableHeader.cells.map(c => c.value)
113
113
  for (const example of examples.tableBody) {
@@ -5,6 +5,7 @@ const getInjectedArguments = (fn, test) => {
5
5
  const testArgs = {}
6
6
  const params = parser.getParams(fn) || []
7
7
  const objects = container.support()
8
+
8
9
  for (const key of params) {
9
10
  testArgs[key] = {}
10
11
  if (test && test.inject && test.inject[key]) {
@@ -18,6 +19,10 @@ const getInjectedArguments = (fn, test) => {
18
19
  testArgs[key] = container.support(key)
19
20
  }
20
21
 
22
+ if (test) {
23
+ testArgs.suite = test?.parent
24
+ testArgs.test = test
25
+ }
21
26
  return testArgs
22
27
  }
23
28
 
package/lib/mocha/test.js CHANGED
@@ -133,8 +133,10 @@ function cloneTest(test) {
133
133
  return deserializeTest(serializeTest(test))
134
134
  }
135
135
 
136
- function testToFileName(test) {
137
- let fileName = clearString(test.title)
136
+ function testToFileName(test, suffix = '') {
137
+ let fileName = test.title
138
+
139
+ if (suffix) fileName = `${fileName}_${suffix}`
138
140
  // remove tags with empty string (disable for now)
139
141
  // fileName = fileName.replace(/\@\w+/g, '')
140
142
  fileName = fileName.slice(0, 100)
@@ -146,6 +148,7 @@ function testToFileName(test) {
146
148
  // if (test.parent && test.parent.title) {
147
149
  // fileName = `${clearString(test.parent.title)}_${fileName}`
148
150
  // }
151
+ fileName = clearString(fileName).slice(0, 100)
149
152
  return fileName
150
153
  }
151
154
 
@@ -60,7 +60,7 @@ const defaultConfig = {
60
60
 
61
61
  If there is no groups of tests, say: "No patterns found"
62
62
  Preserve error messages but cut them if they are too long.
63
- Respond clearly and directly, without introductory words or phrases like Of course,’ Here is the answer,’ etc.
63
+ Respond clearly and directly, without introductory words or phrases like 'Of course,' 'Here is the answer,' etc.
64
64
  Do not list more than 3 errors in the group.
65
65
  If you identify that all tests in the group have the same tag, add this tag to the group report, otherwise ignore TAG section.
66
66
  If you identify that all tests in the group have the same suite, add this suite to the group report, otherwise ignore SUITE section.
@@ -160,9 +160,56 @@ const defaultConfig = {
160
160
  }
161
161
 
162
162
  /**
163
+ * CodeceptJS Analyze Plugin - Uses AI to analyze test failures and provide insights
163
164
  *
164
- * @param {*} config
165
- * @returns
165
+ * This plugin analyzes failed tests using AI to provide detailed explanations and group similar failures.
166
+ * When enabled with --ai flag, it generates reports after test execution.
167
+ *
168
+ * #### Usage
169
+ *
170
+ * ```js
171
+ * // in codecept.conf.js
172
+ * exports.config = {
173
+ * plugins: {
174
+ * analyze: {
175
+ * enabled: true,
176
+ * clusterize: 5,
177
+ * analyze: 2,
178
+ * vision: false
179
+ * }
180
+ * }
181
+ * }
182
+ * ```
183
+ *
184
+ * #### Configuration
185
+ *
186
+ * * `clusterize` (number) - minimum number of failures to trigger clustering analysis. Default: 5
187
+ * * `analyze` (number) - maximum number of individual test failures to analyze in detail. Default: 2
188
+ * * `vision` (boolean) - enables visual analysis of test screenshots. Default: false
189
+ * * `categories` (array) - list of failure categories for classification. Defaults to:
190
+ * - Browser connection error / browser crash
191
+ * - Network errors (server error, timeout, etc)
192
+ * - HTML / page elements (not found, not visible, etc)
193
+ * - Navigation errors (404, etc)
194
+ * - Code errors (syntax error, JS errors, etc)
195
+ * - Library & framework errors
196
+ * - Data errors (password incorrect, invalid format, etc)
197
+ * - Assertion failures
198
+ * - Other errors
199
+ * * `prompts` (object) - customize AI prompts for analysis
200
+ * - `clusterize` - prompt for clustering analysis
201
+ * - `analyze` - prompt for individual test analysis
202
+ *
203
+ * #### Features
204
+ *
205
+ * * Groups similar failures when number of failures >= clusterize value
206
+ * * Provides detailed analysis of individual failures
207
+ * * Analyzes screenshots if vision=true and screenshots are available
208
+ * * Classifies failures into predefined categories
209
+ * * Suggests possible causes and solutions
210
+ *
211
+ * @param {Object} config - Plugin configuration
212
+ * @returns {void}
166
213
  */
167
214
  module.exports = function (config = {}) {
168
215
  config = Object.assign(defaultConfig, config)
@@ -0,0 +1,435 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { fileExists } = require('../utils')
4
+ const CommentStep = require('../step/comment')
5
+ const Section = require('../step/section')
6
+ const container = require('../container')
7
+ const store = require('../store')
8
+ const event = require('../event')
9
+ const recorder = require('../recorder')
10
+ const { debug } = require('../output')
11
+ const { isAsyncFunction } = require('../utils')
12
+
13
+ const defaultUser = {
14
+ fetch: I => I.grabCookie(),
15
+ check: () => {},
16
+ restore: (I, cookies) => {
17
+ I.amOnPage('/') // open a page
18
+ I.setCookie(cookies)
19
+ },
20
+ }
21
+
22
+ const defaultConfig = {
23
+ saveToFile: false,
24
+ inject: 'login',
25
+ }
26
+
27
+ /**
28
+ * Logs user in for the first test and reuses session for next tests.
29
+ * Works by saving cookies into memory or file.
30
+ * If a session expires automatically logs in again.
31
+ *
32
+ * > For better development experience cookies can be saved into file, so a session can be reused while writing tests.
33
+ *
34
+ * #### Usage
35
+ *
36
+ * 1. Enable this plugin and configure as described below
37
+ * 2. Define user session names (example: `user`, `editor`, `admin`, etc).
38
+ * 3. Define how users are logged in and how to check that user is logged in
39
+ * 4. Use `login` object inside your tests to log in:
40
+ *
41
+ * ```js
42
+ * // inside a test file
43
+ * // use login to inject auto-login function
44
+ * Feature('Login');
45
+ *
46
+ * Before(({ login }) => {
47
+ * login('user'); // login using user session
48
+ * });
49
+ *
50
+ * // Alternatively log in for one scenario.
51
+ * Scenario('log me in', ( { I, login } ) => {
52
+ * login('admin');
53
+ * I.see('I am logged in');
54
+ * });
55
+ * ```
56
+ *
57
+ * #### Configuration
58
+ *
59
+ * * `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.
60
+ * * `inject` (default: `login`) - name of the login function to use
61
+ * * `users` - an array containing different session names and functions to:
62
+ * * `login` - sign in into the system
63
+ * * `check` - check that user is logged in
64
+ * * `fetch` - to get current cookies (by default `I.grabCookie()`)
65
+ * * `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)
66
+ *
67
+ * #### How It Works
68
+ *
69
+ * 1. `restore` method is executed. It should open a page and set credentials.
70
+ * 2. `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.
71
+ * 3. If `restore` and `check` were not successful, `login` is executed
72
+ * 4. `login` should fill in login form
73
+ * 5. After successful login, `fetch` is executed to save cookies into memory or file.
74
+ *
75
+ * #### Example: Simple login
76
+ *
77
+ * ```js
78
+ * auth: {
79
+ * enabled: true,
80
+ * saveToFile: true,
81
+ * inject: 'login',
82
+ * users: {
83
+ * admin: {
84
+ * // loginAdmin function is defined in `steps_file.js`
85
+ * login: (I) => I.loginAdmin(),
86
+ * // if we see `Admin` on page, we assume we are logged in
87
+ * check: (I) => {
88
+ * I.amOnPage('/');
89
+ * I.see('Admin');
90
+ * }
91
+ * }
92
+ * }
93
+ * }
94
+ * ```
95
+ *
96
+ * #### Example: Multiple users
97
+ *
98
+ * ```js
99
+ * auth: {
100
+ * enabled: true,
101
+ * saveToFile: true,
102
+ * inject: 'loginAs', // use `loginAs` instead of login
103
+ * users: {
104
+ * user: {
105
+ * login: (I) => {
106
+ * I.amOnPage('/login');
107
+ * I.fillField('email', 'user@site.com');
108
+ * I.fillField('password', '123456');
109
+ * I.click('Login');
110
+ * },
111
+ * check: (I) => {
112
+ * I.amOnPage('/');
113
+ * I.see('User', '.navbar');
114
+ * },
115
+ * },
116
+ * admin: {
117
+ * login: (I) => {
118
+ * I.amOnPage('/login');
119
+ * I.fillField('email', 'admin@site.com');
120
+ * I.fillField('password', '123456');
121
+ * I.click('Login');
122
+ * },
123
+ * check: (I) => {
124
+ * I.amOnPage('/');
125
+ * I.see('Admin', '.navbar');
126
+ * },
127
+ * },
128
+ * }
129
+ * }
130
+ * ```
131
+ *
132
+ * #### Example: Keep cookies between tests
133
+ *
134
+ * If you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.
135
+ * But you need to login once work until session expires.
136
+ * For this case, disable `fetch` and `restore` methods.
137
+ *
138
+ * ```js
139
+ * helpers: {
140
+ * WebDriver: {
141
+ * // config goes here
142
+ * keepCookies: true; // keep cookies for all tests
143
+ * }
144
+ * },
145
+ * plugins: {
146
+ * auth: {
147
+ * users: {
148
+ * admin: {
149
+ * login: (I) => {
150
+ * I.amOnPage('/login');
151
+ * I.fillField('email', 'admin@site.com');
152
+ * I.fillField('password', '123456');
153
+ * I.click('Login');
154
+ * },
155
+ * check: (I) => {
156
+ * I.amOnPage('/dashboard');
157
+ * I.see('Admin', '.navbar');
158
+ * },
159
+ * fetch: () => {}, // empty function
160
+ * restore: () => {}, // empty funciton
161
+ * }
162
+ * }
163
+ * }
164
+ * }
165
+ * ```
166
+ *
167
+ * #### Example: Getting sessions from local storage
168
+ *
169
+ * If your session is stored in local storage instead of cookies you still can obtain sessions.
170
+ *
171
+ * ```js
172
+ * plugins: {
173
+ * auth: {
174
+ * admin: {
175
+ * login: (I) => I.loginAsAdmin(),
176
+ * check: (I) => I.see('Admin', '.navbar'),
177
+ * fetch: (I) => {
178
+ * return I.executeScript(() => localStorage.getItem('session_id'));
179
+ * },
180
+ * restore: (I, session) => {
181
+ * I.amOnPage('/');
182
+ * I.executeScript((session) => localStorage.setItem('session_id', session), session);
183
+ * },
184
+ * }
185
+ * }
186
+ * }
187
+ * ```
188
+ *
189
+ * #### Tips: Using async function in the auth
190
+ *
191
+ * If you use async functions in the auth plugin, login function should be used with `await` keyword.
192
+ *
193
+ * ```js
194
+ * auth: {
195
+ * enabled: true,
196
+ * saveToFile: true,
197
+ * inject: 'login',
198
+ * users: {
199
+ * admin: {
200
+ * login: async (I) => { // If you use async function in the auth plugin
201
+ * const phrase = await I.grabTextFrom('#phrase')
202
+ * I.fillField('username', 'admin'),
203
+ * I.fillField('password', 'password')
204
+ * I.fillField('phrase', phrase)
205
+ * },
206
+ * check: (I) => {
207
+ * I.amOnPage('/');
208
+ * I.see('Admin');
209
+ * },
210
+ * }
211
+ * }
212
+ * }
213
+ * ```
214
+ *
215
+ * ```js
216
+ * Scenario('login', async ( {I, login} ) => {
217
+ * await login('admin') // you should use `await`
218
+ * })
219
+ * ```
220
+ *
221
+ * #### Tips: Using session to validate user
222
+ *
223
+ * Instead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`
224
+ *
225
+ * ```js
226
+ * auth: {
227
+ * enabled: true,
228
+ * saveToFile: true,
229
+ * inject: 'login',
230
+ * users: {
231
+ * admin: {
232
+ * login: async (I) => { // If you use async function in the auth plugin
233
+ * const phrase = await I.grabTextFrom('#phrase')
234
+ * I.fillField('username', 'admin'),
235
+ * I.fillField('password', 'password')
236
+ * I.fillField('phrase', phrase)
237
+ * },
238
+ * check: (I, session) => {
239
+ * // Throwing an error in `check` will make CodeceptJS perform the login step for the user
240
+ * if (session.profile.email !== the.email.you.expect@some-mail.com) {
241
+ * throw new Error ('Wrong user signed in');
242
+ * }
243
+ * },
244
+ * }
245
+ * }
246
+ * }
247
+ * ```
248
+ *
249
+ * ```js
250
+ * Scenario('login', async ( {I, login} ) => {
251
+ * await login('admin') // you should use `await`
252
+ * })
253
+ *
254
+ *
255
+ */
256
+ module.exports = function (config) {
257
+ config = Object.assign(defaultConfig, config)
258
+ Object.keys(config.users).map(
259
+ u =>
260
+ (config.users[u] = {
261
+ ...defaultUser,
262
+ ...config.users[u],
263
+ }),
264
+ )
265
+
266
+ if (config.saveToFile) {
267
+ // loading from file
268
+ loadCookiesFromFile(config)
269
+ }
270
+
271
+ const loginFunction = async name => {
272
+ const I = container.support('I')
273
+ const userSession = config.users[name]
274
+
275
+ if (!userSession) {
276
+ throw new Error(`User '${name}' was not configured for authorization in auth plugin. Add it to the plugin config`)
277
+ }
278
+
279
+ const test = store.currentTest
280
+
281
+ // we are in BeforeSuite hook
282
+ if (!test) {
283
+ enableAuthBeforeEachTest(name)
284
+ return
285
+ }
286
+
287
+ const section = new Section(`I am logged in as ${name}`)
288
+
289
+ if (config.saveToFile && !store[`${name}_session`]) {
290
+ loadCookiesFromFile(config)
291
+ }
292
+
293
+ if (isPlaywrightSession() && test?.opts?.cookies) {
294
+ if (test.opts.user == name) {
295
+ debug(`Cookies already loaded for ${name}`)
296
+
297
+ alreadyLoggedIn(name)
298
+ return
299
+ } else {
300
+ debug(`Cookies already loaded for ${test.opts.user}, but not for ${name}`)
301
+ await I.deleteCookie()
302
+ }
303
+ }
304
+
305
+ section.start()
306
+
307
+ const cookies = store[`${name}_session`]
308
+ const shouldAwait = isAsyncFunction(userSession.login) || isAsyncFunction(userSession.restore) || isAsyncFunction(userSession.check)
309
+
310
+ const loginAndSave = async () => {
311
+ if (shouldAwait) {
312
+ await userSession.login(I)
313
+ } else {
314
+ userSession.login(I)
315
+ }
316
+
317
+ section.end()
318
+ const cookies = await userSession.fetch(I)
319
+ if (!cookies) {
320
+ debug("Cannot save user session with empty cookies from auto login's fetch method")
321
+ return
322
+ }
323
+ if (config.saveToFile) {
324
+ debug(`Saved user session into file for ${name}`)
325
+ fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies))
326
+ }
327
+ store[`${name}_session`] = cookies
328
+ }
329
+
330
+ if (!cookies) return loginAndSave()
331
+
332
+ recorder.session.start('check login')
333
+ if (shouldAwait) {
334
+ await userSession.restore(I, cookies)
335
+ await userSession.check(I, cookies)
336
+ } else {
337
+ userSession.restore(I, cookies)
338
+ userSession.check(I, cookies)
339
+ }
340
+ section.end()
341
+ recorder.session.catch(err => {
342
+ debug(`Failed auto login for ${name} due to ${err}`)
343
+ debug('Logging in again')
344
+ recorder.session.start('auto login')
345
+ return loginAndSave()
346
+ .then(() => {
347
+ recorder.add(() => recorder.session.restore('auto login'))
348
+ recorder.catch(() => debug('continue'))
349
+ })
350
+ .catch(err => {
351
+ recorder.session.restore('auto login')
352
+ recorder.session.restore('check login')
353
+ section.end()
354
+ recorder.throw(err)
355
+ })
356
+ })
357
+ recorder.add(() => {
358
+ recorder.session.restore('check login')
359
+ })
360
+
361
+ return recorder.promise()
362
+ }
363
+
364
+ function enableAuthBeforeEachTest(name) {
365
+ const suite = store.currentSuite
366
+ if (!suite) return
367
+
368
+ debug(`enabling auth as ${name} for each test of suite ${suite.title}`)
369
+
370
+ // we are setting test opts so they can be picked up by Playwright if it starts browser for this test
371
+ suite.eachTest(test => {
372
+ // preload from store
373
+ if (store[`${name}_session`]) {
374
+ test.opts.cookies = store[`${name}_session`]
375
+ test.opts.user = name
376
+ return
377
+ }
378
+
379
+ if (!config.saveToFile) return
380
+ const cookieFile = path.join(global.output_dir, `${name}_session.json`)
381
+
382
+ if (!fileExists(cookieFile)) {
383
+ return
384
+ }
385
+
386
+ const context = fs.readFileSync(cookieFile).toString()
387
+ test.opts.cookies = JSON.parse(context)
388
+ test.opts.user = name
389
+ })
390
+
391
+ function runLoginFunctionForTest(test) {
392
+ if (!suite.tests.includes(test)) return
393
+ // let's call this function to ensure that authorization happened
394
+ // if no cookies, it will login and save them
395
+ loginFunction(name)
396
+ }
397
+
398
+ // we are in BeforeSuite hook
399
+ event.dispatcher.on(event.test.started, runLoginFunctionForTest)
400
+ event.dispatcher.on(event.suite.after, () => {
401
+ event.dispatcher.off(event.test.started, runLoginFunctionForTest)
402
+ })
403
+ }
404
+
405
+ // adding this to DI container
406
+ const support = {}
407
+ support[config.inject] = loginFunction
408
+ container.append({ support })
409
+
410
+ return loginFunction
411
+ }
412
+
413
+ function loadCookiesFromFile(config) {
414
+ for (const name in config.users) {
415
+ const fileName = path.join(global.output_dir, `${name}_session.json`)
416
+ if (!fileExists(fileName)) continue
417
+ const data = fs.readFileSync(fileName).toString()
418
+ try {
419
+ store[`${name}_session`] = JSON.parse(data)
420
+ } catch (err) {
421
+ throw new Error(`Could not load session from ${fileName}\n${err}`)
422
+ }
423
+ debug(`Loaded user session for ${name}`)
424
+ }
425
+ }
426
+
427
+ function isPlaywrightSession() {
428
+ return !!container.helpers('Playwright')
429
+ }
430
+
431
+ function alreadyLoggedIn(name) {
432
+ const step = new CommentStep('am logged in as')
433
+ step.actor = 'I'
434
+ return step.addToRecorder([name])
435
+ }
@@ -2,8 +2,8 @@ const Container = require('../container')
2
2
  const store = require('../store')
3
3
  const recorder = require('../recorder')
4
4
  const event = require('../event')
5
- const log = require('../output').log
6
- const supportedHelpers = require('./standardActingHelpers').slice()
5
+ const { log } = require('../output')
6
+ const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
7
7
 
8
8
  const methodsToDelay = ['click', 'fillField', 'checkOption', 'pressKey', 'doubleClick', 'rightClick']
9
9