codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5
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 +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +139 -87
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +262 -220
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +262 -267
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +246 -260
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +151 -170
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +82 -62
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -1
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -276
- package/package.json +76 -70
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +188 -186
- package/typings/promiseBasedTypes.d.ts +18 -705
- package/typings/types.d.ts +301 -804
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
package/lib/plugin/autoLogin.js
CHANGED
|
@@ -1,340 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const { fileExists } = require('../utils')
|
|
4
|
-
const container = require('../container')
|
|
5
|
-
const store = require('../store')
|
|
6
|
-
const recorder = require('../recorder')
|
|
7
|
-
const { debug } = require('../output')
|
|
8
|
-
const isAsyncFunction = require('../utils').isAsyncFunction
|
|
1
|
+
const auth = require('./auth')
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
fetch: (I) => I.grabCookie(),
|
|
12
|
-
check: () => {},
|
|
13
|
-
restore: (I, cookies) => {
|
|
14
|
-
I.amOnPage('/') // open a page
|
|
15
|
-
I.setCookie(cookies)
|
|
16
|
-
},
|
|
17
|
-
}
|
|
3
|
+
console.log('autoLogin plugin was renamed to auth plugin. Please update your config.')
|
|
18
4
|
|
|
19
|
-
|
|
20
|
-
saveToFile: false,
|
|
21
|
-
inject: 'login',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Logs user in for the first test and reuses session for next tests.
|
|
26
|
-
* Works by saving cookies into memory or file.
|
|
27
|
-
* If a session expires automatically logs in again.
|
|
28
|
-
*
|
|
29
|
-
* > For better development experience cookies can be saved into file, so a session can be reused while writing tests.
|
|
30
|
-
*
|
|
31
|
-
* #### Usage
|
|
32
|
-
*
|
|
33
|
-
* 1. Enable this plugin and configure as described below
|
|
34
|
-
* 2. Define user session names (example: `user`, `editor`, `admin`, etc).
|
|
35
|
-
* 3. Define how users are logged in and how to check that user is logged in
|
|
36
|
-
* 4. Use `login` object inside your tests to log in:
|
|
37
|
-
*
|
|
38
|
-
* ```js
|
|
39
|
-
* // inside a test file
|
|
40
|
-
* // use login to inject auto-login function
|
|
41
|
-
* Feature('Login');
|
|
42
|
-
*
|
|
43
|
-
* Before(({ login }) => {
|
|
44
|
-
* login('user'); // login using user session
|
|
45
|
-
* });
|
|
46
|
-
*
|
|
47
|
-
* // Alternatively log in for one scenario.
|
|
48
|
-
* Scenario('log me in', ( { I, login } ) => {
|
|
49
|
-
* login('admin');
|
|
50
|
-
* I.see('I am logged in');
|
|
51
|
-
* });
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* #### Configuration
|
|
55
|
-
*
|
|
56
|
-
* * `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.
|
|
57
|
-
* * `inject` (default: `login`) - name of the login function to use
|
|
58
|
-
* * `users` - an array containing different session names and functions to:
|
|
59
|
-
* * `login` - sign in into the system
|
|
60
|
-
* * `check` - check that user is logged in
|
|
61
|
-
* * `fetch` - to get current cookies (by default `I.grabCookie()`)
|
|
62
|
-
* * `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)
|
|
63
|
-
*
|
|
64
|
-
* #### How It Works
|
|
65
|
-
*
|
|
66
|
-
* 1. `restore` method is executed. It should open a page and set credentials.
|
|
67
|
-
* 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.
|
|
68
|
-
* 3. If `restore` and `check` were not successful, `login` is executed
|
|
69
|
-
* 4. `login` should fill in login form
|
|
70
|
-
* 5. After successful login, `fetch` is executed to save cookies into memory or file.
|
|
71
|
-
*
|
|
72
|
-
* #### Example: Simple login
|
|
73
|
-
*
|
|
74
|
-
* ```js
|
|
75
|
-
* autoLogin: {
|
|
76
|
-
* enabled: true,
|
|
77
|
-
* saveToFile: true,
|
|
78
|
-
* inject: 'login',
|
|
79
|
-
* users: {
|
|
80
|
-
* admin: {
|
|
81
|
-
* // loginAdmin function is defined in `steps_file.js`
|
|
82
|
-
* login: (I) => I.loginAdmin(),
|
|
83
|
-
* // if we see `Admin` on page, we assume we are logged in
|
|
84
|
-
* check: (I) => {
|
|
85
|
-
* I.amOnPage('/');
|
|
86
|
-
* I.see('Admin');
|
|
87
|
-
* }
|
|
88
|
-
* }
|
|
89
|
-
* }
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* #### Example: Multiple users
|
|
94
|
-
*
|
|
95
|
-
* ```js
|
|
96
|
-
* autoLogin: {
|
|
97
|
-
* enabled: true,
|
|
98
|
-
* saveToFile: true,
|
|
99
|
-
* inject: 'loginAs', // use `loginAs` instead of login
|
|
100
|
-
* users: {
|
|
101
|
-
* user: {
|
|
102
|
-
* login: (I) => {
|
|
103
|
-
* I.amOnPage('/login');
|
|
104
|
-
* I.fillField('email', 'user@site.com');
|
|
105
|
-
* I.fillField('password', '123456');
|
|
106
|
-
* I.click('Login');
|
|
107
|
-
* },
|
|
108
|
-
* check: (I) => {
|
|
109
|
-
* I.amOnPage('/');
|
|
110
|
-
* I.see('User', '.navbar');
|
|
111
|
-
* },
|
|
112
|
-
* },
|
|
113
|
-
* admin: {
|
|
114
|
-
* login: (I) => {
|
|
115
|
-
* I.amOnPage('/login');
|
|
116
|
-
* I.fillField('email', 'admin@site.com');
|
|
117
|
-
* I.fillField('password', '123456');
|
|
118
|
-
* I.click('Login');
|
|
119
|
-
* },
|
|
120
|
-
* check: (I) => {
|
|
121
|
-
* I.amOnPage('/');
|
|
122
|
-
* I.see('Admin', '.navbar');
|
|
123
|
-
* },
|
|
124
|
-
* },
|
|
125
|
-
* }
|
|
126
|
-
* }
|
|
127
|
-
* ```
|
|
128
|
-
*
|
|
129
|
-
* #### Example: Keep cookies between tests
|
|
130
|
-
*
|
|
131
|
-
* If you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.
|
|
132
|
-
* But you need to login once work until session expires.
|
|
133
|
-
* For this case, disable `fetch` and `restore` methods.
|
|
134
|
-
*
|
|
135
|
-
* ```js
|
|
136
|
-
* helpers: {
|
|
137
|
-
* WebDriver: {
|
|
138
|
-
* // config goes here
|
|
139
|
-
* keepCookies: true; // keep cookies for all tests
|
|
140
|
-
* }
|
|
141
|
-
* },
|
|
142
|
-
* plugins: {
|
|
143
|
-
* autoLogin: {
|
|
144
|
-
* users: {
|
|
145
|
-
* admin: {
|
|
146
|
-
* login: (I) => {
|
|
147
|
-
* I.amOnPage('/login');
|
|
148
|
-
* I.fillField('email', 'admin@site.com');
|
|
149
|
-
* I.fillField('password', '123456');
|
|
150
|
-
* I.click('Login');
|
|
151
|
-
* },
|
|
152
|
-
* check: (I) => {
|
|
153
|
-
* I.amOnPage('/dashboard');
|
|
154
|
-
* I.see('Admin', '.navbar');
|
|
155
|
-
* },
|
|
156
|
-
* fetch: () => {}, // empty function
|
|
157
|
-
* restore: () => {}, // empty funciton
|
|
158
|
-
* }
|
|
159
|
-
* }
|
|
160
|
-
* }
|
|
161
|
-
* }
|
|
162
|
-
* ```
|
|
163
|
-
*
|
|
164
|
-
* #### Example: Getting sessions from local storage
|
|
165
|
-
*
|
|
166
|
-
* If your session is stored in local storage instead of cookies you still can obtain sessions.
|
|
167
|
-
*
|
|
168
|
-
* ```js
|
|
169
|
-
* plugins: {
|
|
170
|
-
* autoLogin: {
|
|
171
|
-
* admin: {
|
|
172
|
-
* login: (I) => I.loginAsAdmin(),
|
|
173
|
-
* check: (I) => I.see('Admin', '.navbar'),
|
|
174
|
-
* fetch: (I) => {
|
|
175
|
-
* return I.executeScript(() => localStorage.getItem('session_id'));
|
|
176
|
-
* },
|
|
177
|
-
* restore: (I, session) => {
|
|
178
|
-
* I.amOnPage('/');
|
|
179
|
-
* I.executeScript((session) => localStorage.setItem('session_id', session), session);
|
|
180
|
-
* },
|
|
181
|
-
* }
|
|
182
|
-
* }
|
|
183
|
-
* }
|
|
184
|
-
* ```
|
|
185
|
-
*
|
|
186
|
-
* #### Tips: Using async function in the autoLogin
|
|
187
|
-
*
|
|
188
|
-
* If you use async functions in the autoLogin plugin, login function should be used with `await` keyword.
|
|
189
|
-
*
|
|
190
|
-
* ```js
|
|
191
|
-
* autoLogin: {
|
|
192
|
-
* enabled: true,
|
|
193
|
-
* saveToFile: true,
|
|
194
|
-
* inject: 'login',
|
|
195
|
-
* users: {
|
|
196
|
-
* admin: {
|
|
197
|
-
* login: async (I) => { // If you use async function in the autoLogin plugin
|
|
198
|
-
* const phrase = await I.grabTextFrom('#phrase')
|
|
199
|
-
* I.fillField('username', 'admin'),
|
|
200
|
-
* I.fillField('password', 'password')
|
|
201
|
-
* I.fillField('phrase', phrase)
|
|
202
|
-
* },
|
|
203
|
-
* check: (I) => {
|
|
204
|
-
* I.amOnPage('/');
|
|
205
|
-
* I.see('Admin');
|
|
206
|
-
* },
|
|
207
|
-
* }
|
|
208
|
-
* }
|
|
209
|
-
* }
|
|
210
|
-
* ```
|
|
211
|
-
*
|
|
212
|
-
* ```js
|
|
213
|
-
* Scenario('login', async ( {I, login} ) => {
|
|
214
|
-
* await login('admin') // you should use `await`
|
|
215
|
-
* })
|
|
216
|
-
* ```
|
|
217
|
-
*
|
|
218
|
-
* #### Tips: Using session to validate user
|
|
219
|
-
*
|
|
220
|
-
* Instead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`
|
|
221
|
-
*
|
|
222
|
-
* ```js
|
|
223
|
-
* autoLogin: {
|
|
224
|
-
* enabled: true,
|
|
225
|
-
* saveToFile: true,
|
|
226
|
-
* inject: 'login',
|
|
227
|
-
* users: {
|
|
228
|
-
* admin: {
|
|
229
|
-
* login: async (I) => { // If you use async function in the autoLogin plugin
|
|
230
|
-
* const phrase = await I.grabTextFrom('#phrase')
|
|
231
|
-
* I.fillField('username', 'admin'),
|
|
232
|
-
* I.fillField('password', 'password')
|
|
233
|
-
* I.fillField('phrase', phrase)
|
|
234
|
-
* },
|
|
235
|
-
* check: (I, session) => {
|
|
236
|
-
* // Throwing an error in `check` will make CodeceptJS perform the login step for the user
|
|
237
|
-
* if (session.profile.email !== the.email.you.expect@some-mail.com) {
|
|
238
|
-
* throw new Error ('Wrong user signed in');
|
|
239
|
-
* }
|
|
240
|
-
* },
|
|
241
|
-
* }
|
|
242
|
-
* }
|
|
243
|
-
* }
|
|
244
|
-
* ```
|
|
245
|
-
*
|
|
246
|
-
* ```js
|
|
247
|
-
* Scenario('login', async ( {I, login} ) => {
|
|
248
|
-
* await login('admin') // you should use `await`
|
|
249
|
-
* })
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*/
|
|
253
|
-
module.exports = function (config) {
|
|
254
|
-
config = Object.assign(defaultConfig, config)
|
|
255
|
-
Object.keys(config.users).map(
|
|
256
|
-
(u) =>
|
|
257
|
-
(config.users[u] = {
|
|
258
|
-
...defaultUser,
|
|
259
|
-
...config.users[u],
|
|
260
|
-
}),
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
if (config.saveToFile) {
|
|
264
|
-
// loading from file
|
|
265
|
-
for (const name in config.users) {
|
|
266
|
-
const fileName = path.join(global.output_dir, `${name}_session.json`)
|
|
267
|
-
if (!fileExists(fileName)) continue
|
|
268
|
-
const data = fs.readFileSync(fileName).toString()
|
|
269
|
-
try {
|
|
270
|
-
store[`${name}_session`] = JSON.parse(data)
|
|
271
|
-
} catch (err) {
|
|
272
|
-
throw new Error(`Could not load session from ${fileName}\n${err}`)
|
|
273
|
-
}
|
|
274
|
-
debug(`Loaded user session for ${name}`)
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const loginFunction = async (name) => {
|
|
279
|
-
const userSession = config.users[name]
|
|
280
|
-
const I = container.support('I')
|
|
281
|
-
const cookies = store[`${name}_session`]
|
|
282
|
-
const shouldAwait =
|
|
283
|
-
isAsyncFunction(userSession.login) || isAsyncFunction(userSession.restore) || isAsyncFunction(userSession.check)
|
|
284
|
-
|
|
285
|
-
const loginAndSave = async () => {
|
|
286
|
-
if (shouldAwait) {
|
|
287
|
-
await userSession.login(I)
|
|
288
|
-
} else {
|
|
289
|
-
userSession.login(I)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const cookies = await userSession.fetch(I)
|
|
293
|
-
if (!cookies) {
|
|
294
|
-
debug("Cannot save user session with empty cookies from auto login's fetch method")
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
if (config.saveToFile) {
|
|
298
|
-
debug(`Saved user session into file for ${name}`)
|
|
299
|
-
fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies))
|
|
300
|
-
}
|
|
301
|
-
store[`${name}_session`] = cookies
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (!cookies) return loginAndSave()
|
|
305
|
-
|
|
306
|
-
recorder.session.start('check login')
|
|
307
|
-
if (shouldAwait) {
|
|
308
|
-
await userSession.restore(I, cookies)
|
|
309
|
-
await userSession.check(I, cookies)
|
|
310
|
-
} else {
|
|
311
|
-
userSession.restore(I, cookies)
|
|
312
|
-
userSession.check(I, cookies)
|
|
313
|
-
}
|
|
314
|
-
recorder.session.catch((err) => {
|
|
315
|
-
debug(`Failed auto login for ${name} due to ${err}`)
|
|
316
|
-
debug('Logging in again')
|
|
317
|
-
recorder.session.start('auto login')
|
|
318
|
-
return loginAndSave()
|
|
319
|
-
.then(() => {
|
|
320
|
-
recorder.add(() => recorder.session.restore('auto login'))
|
|
321
|
-
recorder.catch(() => debug('continue'))
|
|
322
|
-
})
|
|
323
|
-
.catch((err) => {
|
|
324
|
-
recorder.session.restore('auto login')
|
|
325
|
-
recorder.session.restore('check login')
|
|
326
|
-
recorder.throw(err)
|
|
327
|
-
})
|
|
328
|
-
})
|
|
329
|
-
recorder.add(() => {
|
|
330
|
-
recorder.session.restore('check login')
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
return recorder.promise()
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// adding this to DI container
|
|
337
|
-
const support = {}
|
|
338
|
-
support[config.inject] = loginFunction
|
|
339
|
-
container.append({ support })
|
|
340
|
-
}
|
|
5
|
+
module.exports = auth
|
|
@@ -7,6 +7,8 @@ let currentCommentStep
|
|
|
7
7
|
const defaultGlobalName = '__'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* This plugin is **deprecated**, use `Section` instead.
|
|
11
|
+
*
|
|
10
12
|
* Add descriptive nested steps for your tests:
|
|
11
13
|
*
|
|
12
14
|
* ```js
|
|
@@ -100,11 +102,14 @@ const defaultGlobalName = '__'
|
|
|
100
102
|
* ```
|
|
101
103
|
*/
|
|
102
104
|
module.exports = function (config) {
|
|
105
|
+
console.log('commentStep is deprecated, disable it and use Section instead')
|
|
106
|
+
console.log('const { Section: __ } = require("codeceptjs/steps")')
|
|
107
|
+
|
|
103
108
|
event.dispatcher.on(event.test.started, () => {
|
|
104
109
|
currentCommentStep = null
|
|
105
110
|
})
|
|
106
111
|
|
|
107
|
-
event.dispatcher.on(event.step.started,
|
|
112
|
+
event.dispatcher.on(event.step.started, step => {
|
|
108
113
|
if (currentCommentStep) {
|
|
109
114
|
const metaStep = getRootMetaStep(step)
|
|
110
115
|
|
package/lib/plugin/coverage.js
CHANGED
|
@@ -15,7 +15,7 @@ const supportedHelpers = ['Puppeteer', 'Playwright', 'WebDriver']
|
|
|
15
15
|
|
|
16
16
|
const v8CoverageHelpers = {
|
|
17
17
|
Playwright: {
|
|
18
|
-
startCoverage: async
|
|
18
|
+
startCoverage: async page => {
|
|
19
19
|
await Promise.all([
|
|
20
20
|
page.coverage.startJSCoverage({
|
|
21
21
|
resetOnNavigation: false,
|
|
@@ -26,16 +26,13 @@ const v8CoverageHelpers = {
|
|
|
26
26
|
])
|
|
27
27
|
},
|
|
28
28
|
takeCoverage: async (page, coverageReport) => {
|
|
29
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
30
|
-
page.coverage.stopJSCoverage(),
|
|
31
|
-
page.coverage.stopCSSCoverage(),
|
|
32
|
-
])
|
|
29
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
33
30
|
const coverageList = [...jsCoverage, ...cssCoverage]
|
|
34
31
|
await coverageReport.add(coverageList)
|
|
35
32
|
},
|
|
36
33
|
},
|
|
37
34
|
Puppeteer: {
|
|
38
|
-
startCoverage: async
|
|
35
|
+
startCoverage: async page => {
|
|
39
36
|
await Promise.all([
|
|
40
37
|
page.coverage.startJSCoverage({
|
|
41
38
|
resetOnNavigation: false,
|
|
@@ -47,13 +44,10 @@ const v8CoverageHelpers = {
|
|
|
47
44
|
])
|
|
48
45
|
},
|
|
49
46
|
takeCoverage: async (page, coverageReport) => {
|
|
50
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
51
|
-
page.coverage.stopJSCoverage(),
|
|
52
|
-
page.coverage.stopCSSCoverage(),
|
|
53
|
-
])
|
|
47
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
54
48
|
// to raw V8 script coverage
|
|
55
49
|
const coverageList = [
|
|
56
|
-
...jsCoverage.map(
|
|
50
|
+
...jsCoverage.map(it => {
|
|
57
51
|
return {
|
|
58
52
|
source: it.text,
|
|
59
53
|
...it.rawScriptCoverage,
|
|
@@ -65,7 +59,7 @@ const v8CoverageHelpers = {
|
|
|
65
59
|
},
|
|
66
60
|
},
|
|
67
61
|
WebDriver: {
|
|
68
|
-
startCoverage: async
|
|
62
|
+
startCoverage: async page => {
|
|
69
63
|
await Promise.all([
|
|
70
64
|
page.coverage.startJSCoverage({
|
|
71
65
|
resetOnNavigation: false,
|
|
@@ -77,13 +71,10 @@ const v8CoverageHelpers = {
|
|
|
77
71
|
])
|
|
78
72
|
},
|
|
79
73
|
takeCoverage: async (page, coverageReport) => {
|
|
80
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
81
|
-
page.coverage.stopJSCoverage(),
|
|
82
|
-
page.coverage.stopCSSCoverage(),
|
|
83
|
-
])
|
|
74
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
84
75
|
// to raw V8 script coverage
|
|
85
76
|
const coverageList = [
|
|
86
|
-
...jsCoverage.map(
|
|
77
|
+
...jsCoverage.map(it => {
|
|
87
78
|
return {
|
|
88
79
|
source: it.text,
|
|
89
80
|
...it.rawScriptCoverage,
|
|
@@ -131,7 +122,7 @@ module.exports = function (config) {
|
|
|
131
122
|
let coverageRunning = false
|
|
132
123
|
|
|
133
124
|
const v8Names = Object.keys(v8CoverageHelpers)
|
|
134
|
-
const helperName = Object.keys(helpers).find(
|
|
125
|
+
const helperName = Object.keys(helpers).find(it => v8Names.includes(it))
|
|
135
126
|
if (!helperName) {
|
|
136
127
|
console.error(`Coverage is only supported in ${supportedHelpers.join(' or ')}`)
|
|
137
128
|
// no helpers for screenshot
|
|
@@ -180,7 +171,7 @@ module.exports = function (config) {
|
|
|
180
171
|
})
|
|
181
172
|
|
|
182
173
|
// Save coverage data after every test run
|
|
183
|
-
event.dispatcher.on(event.test.after,
|
|
174
|
+
event.dispatcher.on(event.test.after, test => {
|
|
184
175
|
recorder.add(
|
|
185
176
|
'take coverage',
|
|
186
177
|
async () => {
|
|
@@ -111,7 +111,7 @@ const defaultConfig = {
|
|
|
111
111
|
* I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
|
-
module.exports =
|
|
114
|
+
module.exports = config => {
|
|
115
115
|
config = { ...defaultConfig, ...config }
|
|
116
116
|
|
|
117
117
|
Locator.addFilter((value, locatorObj) => {
|
|
@@ -125,7 +125,7 @@ module.exports = (config) => {
|
|
|
125
125
|
if (config.strategy.toLowerCase() === 'xpath') {
|
|
126
126
|
locatorObj.value = `.//*[${[]
|
|
127
127
|
.concat(config.attribute)
|
|
128
|
-
.map(
|
|
128
|
+
.map(attr => `@${attr}=${xpathLocator.literal(val)}`)
|
|
129
129
|
.join(' or ')}]`
|
|
130
130
|
locatorObj.type = 'xpath'
|
|
131
131
|
}
|
|
@@ -133,7 +133,7 @@ module.exports = (config) => {
|
|
|
133
133
|
if (config.strategy.toLowerCase() === 'css') {
|
|
134
134
|
locatorObj.value = []
|
|
135
135
|
.concat(config.attribute)
|
|
136
|
-
.map(
|
|
136
|
+
.map(attr => `[${attr}=${val}]`)
|
|
137
137
|
.join(',')
|
|
138
138
|
locatorObj.type = 'css'
|
|
139
139
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const event = require('../event')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sample custom reporter for CodeceptJS.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function (config) {
|
|
7
|
+
event.dispatcher.on(event.hook.finished, hook => {
|
|
8
|
+
if (config.onHookFinished) {
|
|
9
|
+
config.onHookFinished(hook)
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
event.dispatcher.on(event.test.before, test => {
|
|
14
|
+
if (config.onTestBefore) {
|
|
15
|
+
config.onTestBefore(test)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
20
|
+
if (config.onTestFailed) {
|
|
21
|
+
config.onTestFailed(test, err)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
event.dispatcher.on(event.test.passed, test => {
|
|
26
|
+
if (config.onTestPassed) {
|
|
27
|
+
config.onTestPassed(test)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
event.dispatcher.on(event.test.skipped, test => {
|
|
32
|
+
if (config.onTestSkipped) {
|
|
33
|
+
config.onTestSkipped(test)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
event.dispatcher.on(event.test.finished, test => {
|
|
38
|
+
if (config.onTestFinished) {
|
|
39
|
+
config.onTestFinished(test)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
event.dispatcher.on(event.all.result, result => {
|
|
44
|
+
if (config.onResult) {
|
|
45
|
+
config.onResult(result)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.save) {
|
|
49
|
+
result.save()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -72,7 +72,7 @@ function eachElement(purpose, locator, fn) {
|
|
|
72
72
|
if (store.dryRun) return
|
|
73
73
|
const helpers = Object.values(container.helpers())
|
|
74
74
|
|
|
75
|
-
const helper = helpers.filter(
|
|
75
|
+
const helper = helpers.filter(h => !!h._locate)[0]
|
|
76
76
|
|
|
77
77
|
if (!helper) {
|
|
78
78
|
throw new Error('No helper enabled with _locate method with returns a list of elements.')
|
|
@@ -40,7 +40,7 @@ const transform = require('../transform')
|
|
|
40
40
|
*
|
|
41
41
|
*/
|
|
42
42
|
module.exports = function (config) {
|
|
43
|
-
transform.addTransformerBeforeAll('gherkin.examples',
|
|
43
|
+
transform.addTransformerBeforeAll('gherkin.examples', value => {
|
|
44
44
|
if (typeof value === 'string' && value.length > 0) {
|
|
45
45
|
return faker.helpers.fake(value)
|
|
46
46
|
}
|
package/lib/plugin/heal.js
CHANGED
|
@@ -5,6 +5,7 @@ const event = require('../event')
|
|
|
5
5
|
const output = require('../output')
|
|
6
6
|
const heal = require('../heal')
|
|
7
7
|
const store = require('../store')
|
|
8
|
+
const container = require('../container')
|
|
8
9
|
|
|
9
10
|
const defaultConfig = {
|
|
10
11
|
healLimit: 2,
|
|
@@ -31,10 +32,7 @@ const defaultConfig = {
|
|
|
31
32
|
module.exports = function (config = {}) {
|
|
32
33
|
if (store.debugMode && !process.env.DEBUG) {
|
|
33
34
|
event.dispatcher.on(event.test.failed, () => {
|
|
34
|
-
output.plugin(
|
|
35
|
-
'heal',
|
|
36
|
-
'Healing is disabled in --debug mode, use DEBUG="codeceptjs:heal" to enable it in debug mode',
|
|
37
|
-
)
|
|
35
|
+
output.plugin('heal', 'Healing is disabled in --debug mode, use DEBUG="codeceptjs:heal" to enable it in debug mode')
|
|
38
36
|
})
|
|
39
37
|
return
|
|
40
38
|
}
|
|
@@ -48,21 +46,21 @@ module.exports = function (config = {}) {
|
|
|
48
46
|
|
|
49
47
|
config = Object.assign(defaultConfig, config)
|
|
50
48
|
|
|
51
|
-
event.dispatcher.on(event.test.before,
|
|
49
|
+
event.dispatcher.on(event.test.before, test => {
|
|
52
50
|
currentTest = test
|
|
53
51
|
healedSteps = 0
|
|
54
52
|
caughtError = null
|
|
55
53
|
})
|
|
56
54
|
|
|
57
|
-
event.dispatcher.on(event.step.started,
|
|
55
|
+
event.dispatcher.on(event.step.started, step => (currentStep = step))
|
|
58
56
|
|
|
59
|
-
event.dispatcher.on(event.step.after,
|
|
57
|
+
event.dispatcher.on(event.step.after, step => {
|
|
60
58
|
if (isHealing) return
|
|
61
59
|
if (healTries >= config.healLimit) return // out of limit
|
|
62
60
|
|
|
63
61
|
if (!heal.hasCorrespondingRecipes(step)) return
|
|
64
62
|
|
|
65
|
-
recorder.catchWithoutStop(async
|
|
63
|
+
recorder.catchWithoutStop(async err => {
|
|
66
64
|
isHealing = true
|
|
67
65
|
if (caughtError === err) throw err // avoid double handling
|
|
68
66
|
caughtError = err
|
|
@@ -99,7 +97,7 @@ module.exports = function (config = {}) {
|
|
|
99
97
|
|
|
100
98
|
print(`${colors.bold(heal.fixes.length)} ${heal.fixes.length === 1 ? 'step was' : 'steps were'} healed`)
|
|
101
99
|
|
|
102
|
-
const suggestions = heal.fixes.filter(
|
|
100
|
+
const suggestions = heal.fixes.filter(fix => fix.recipe && heal.recipes[fix.recipe].suggest)
|
|
103
101
|
|
|
104
102
|
if (!suggestions.length) return
|
|
105
103
|
|
|
@@ -118,4 +116,33 @@ module.exports = function (config = {}) {
|
|
|
118
116
|
i++
|
|
119
117
|
}
|
|
120
118
|
})
|
|
119
|
+
|
|
120
|
+
event.dispatcher.on(event.workers.result, result => {
|
|
121
|
+
const { print } = output
|
|
122
|
+
|
|
123
|
+
const healedTests = Object.values(result.tests)
|
|
124
|
+
.flat()
|
|
125
|
+
.filter(test => test.notes.some(note => note.type === 'heal'))
|
|
126
|
+
if (!healedTests.length) return
|
|
127
|
+
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
print('')
|
|
130
|
+
print('===================')
|
|
131
|
+
print(colors.bold.green('Self-Healing Report:'))
|
|
132
|
+
|
|
133
|
+
print('')
|
|
134
|
+
print('Suggested changes:')
|
|
135
|
+
print('')
|
|
136
|
+
|
|
137
|
+
healedTests.forEach(test => {
|
|
138
|
+
print(`${colors.bold.magenta(test.title)}`)
|
|
139
|
+
test.notes
|
|
140
|
+
.filter(note => note.type === 'heal')
|
|
141
|
+
.forEach(note => {
|
|
142
|
+
print(note.text)
|
|
143
|
+
print('')
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
}, 0)
|
|
147
|
+
})
|
|
121
148
|
}
|