codeceptjs 4.0.0-beta.2 → 4.0.0-beta.20
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 +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +71 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +262 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +301 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +109 -50
- package/lib/container.js +641 -261
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +47 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- 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 +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +334 -0
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/loaderCheck.js +124 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils/typescript.js +237 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +124 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- 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 +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -875
- package/typings/types.d.ts +547 -992
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin'
|
|
2
|
+
import { IdGenerator } from '@cucumber/messages'
|
|
3
|
+
import { Context, Suite } from 'mocha'
|
|
4
|
+
import debug from 'debug'
|
|
5
|
+
const debugBdd = debug('codeceptjs:bdd')
|
|
6
|
+
|
|
7
|
+
import event from '../event.js'
|
|
8
|
+
import { injected, setup, teardown, suiteSetup, suiteTeardown } from './asyncWrapper.js'
|
|
9
|
+
import step from '../step/base.js'
|
|
10
|
+
import MetaStep from '../step/meta.js'
|
|
11
|
+
import DataTableArgument from '../data/dataTableArgument.js'
|
|
12
|
+
import transform from '../transform.js'
|
|
13
|
+
import { enhanceMochaSuite } from './suite.js'
|
|
14
|
+
import { createTest } from './test.js'
|
|
15
|
+
import { matchStep } from './bdd.js'
|
|
16
|
+
|
|
17
|
+
const uuidFn = IdGenerator.uuid()
|
|
18
|
+
const builder = new AstBuilder(uuidFn)
|
|
19
|
+
const matcher = new GherkinClassicTokenMatcher()
|
|
20
|
+
const parser = new Parser(builder, matcher)
|
|
21
|
+
parser.stopAtFirstError = false
|
|
22
|
+
|
|
23
|
+
const gherkinParser = (text, file) => {
|
|
24
|
+
const ast = parser.parse(text)
|
|
25
|
+
let currentLanguage
|
|
26
|
+
|
|
27
|
+
if (ast.feature) {
|
|
28
|
+
// Ensure translations are loaded before trying to access them
|
|
29
|
+
currentLanguage = getTranslation(ast.feature.language)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!ast.feature) {
|
|
33
|
+
throw new Error(`No 'Features' available in Gherkin '${file}' provided!`)
|
|
34
|
+
}
|
|
35
|
+
const suite = new Suite(ast.feature.name, new Context())
|
|
36
|
+
enhanceMochaSuite(suite)
|
|
37
|
+
const tags = ast.feature.tags.map(t => t.name)
|
|
38
|
+
suite.title = `${suite.title} ${tags.join(' ')}`.trim()
|
|
39
|
+
suite.tags = tags || []
|
|
40
|
+
suite.comment = ast.feature.description
|
|
41
|
+
suite.feature = ast.feature
|
|
42
|
+
suite.file = file
|
|
43
|
+
suite.timeout(0)
|
|
44
|
+
|
|
45
|
+
suite.beforeEach('codeceptjs.before', function () {
|
|
46
|
+
// In Mocha, 'this' refers to the current test in beforeEach/afterEach hooks
|
|
47
|
+
setup(this)(() => {})
|
|
48
|
+
})
|
|
49
|
+
suite.afterEach('codeceptjs.after', function () {
|
|
50
|
+
// In Mocha, 'this' refers to the current test in beforeEach/afterEach hooks
|
|
51
|
+
teardown(this)(() => {})
|
|
52
|
+
})
|
|
53
|
+
suite.beforeAll('codeceptjs.beforeSuite', suiteSetup(suite))
|
|
54
|
+
suite.afterAll('codeceptjs.afterSuite', suiteTeardown(suite))
|
|
55
|
+
|
|
56
|
+
const runSteps = async steps => {
|
|
57
|
+
for (const step of steps) {
|
|
58
|
+
const metaStep = new MetaStep(null, step.text)
|
|
59
|
+
metaStep.actor = step.keyword.trim()
|
|
60
|
+
let helperStep
|
|
61
|
+
const setMetaStep = step => {
|
|
62
|
+
helperStep = step
|
|
63
|
+
if (step.metaStep) {
|
|
64
|
+
if (step.metaStep === metaStep) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
setMetaStep(step.metaStep)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
step.metaStep = metaStep
|
|
71
|
+
}
|
|
72
|
+
const fn = matchStep(step.text)
|
|
73
|
+
|
|
74
|
+
if (step.dataTable) {
|
|
75
|
+
fn.params.push({
|
|
76
|
+
...step.dataTable,
|
|
77
|
+
parse: () => new DataTableArgument(step.dataTable),
|
|
78
|
+
})
|
|
79
|
+
metaStep.comment = `\n${transformTable(step.dataTable)}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (step.docString) {
|
|
83
|
+
fn.params.push(step.docString)
|
|
84
|
+
metaStep.comment = `\n"""\n${step.docString.content}\n"""`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
step.startTime = Date.now()
|
|
88
|
+
step.match = fn.line
|
|
89
|
+
event.emit(event.bddStep.before, step)
|
|
90
|
+
event.emit(event.bddStep.started, metaStep)
|
|
91
|
+
event.dispatcher.prependListener(event.step.before, setMetaStep)
|
|
92
|
+
try {
|
|
93
|
+
debug(`Step '${step.text}' started...`)
|
|
94
|
+
await fn(...fn.params)
|
|
95
|
+
debug('Step passed')
|
|
96
|
+
step.status = 'passed'
|
|
97
|
+
} catch (err) {
|
|
98
|
+
debug(`Step failed: ${err?.message}`)
|
|
99
|
+
step.status = 'failed'
|
|
100
|
+
step.err = err
|
|
101
|
+
throw err
|
|
102
|
+
} finally {
|
|
103
|
+
step.endTime = Date.now()
|
|
104
|
+
event.dispatcher.removeListener(event.step.before, setMetaStep)
|
|
105
|
+
}
|
|
106
|
+
event.emit(event.bddStep.finished, metaStep)
|
|
107
|
+
event.emit(event.bddStep.after, step)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const child of ast.feature.children) {
|
|
112
|
+
if (child.background) {
|
|
113
|
+
suite.beforeEach(
|
|
114
|
+
'Before',
|
|
115
|
+
injected(async () => runSteps(child.background.steps), suite, 'before'),
|
|
116
|
+
)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline === child.scenario.keyword : child.scenario.keyword === 'Scenario Outline')) {
|
|
120
|
+
for (const examples of child.scenario.examples) {
|
|
121
|
+
const fields = examples.tableHeader.cells.map(c => c.value)
|
|
122
|
+
for (const example of examples.tableBody) {
|
|
123
|
+
let exampleSteps = [...child.scenario.steps]
|
|
124
|
+
const current = {}
|
|
125
|
+
for (const index in example.cells) {
|
|
126
|
+
const placeholder = fields[index]
|
|
127
|
+
const value = transform('gherkin.examples', example.cells[index].value)
|
|
128
|
+
example.cells[index].value = value
|
|
129
|
+
current[placeholder] = value
|
|
130
|
+
exampleSteps = exampleSteps.map(step => {
|
|
131
|
+
step = { ...step }
|
|
132
|
+
step.text = step.text.split(`<${placeholder}>`).join(value)
|
|
133
|
+
return step
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name))
|
|
137
|
+
let title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim()
|
|
138
|
+
|
|
139
|
+
for (const [key, value] of Object.entries(current)) {
|
|
140
|
+
if (title.includes(`<${key}>`)) {
|
|
141
|
+
title = title.replace(JSON.stringify(current), '').replace(`<${key}>`, value)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const test = createTest(title, async () => runSteps(addExampleInTable(exampleSteps, current)))
|
|
146
|
+
test.addToSuite(suite)
|
|
147
|
+
test.tags = suite.tags.concat(tags)
|
|
148
|
+
test.file = file
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (child.scenario) {
|
|
155
|
+
const tags = child.scenario.tags.map(t => t.name)
|
|
156
|
+
const title = `${child.scenario.name} ${tags.join(' ')}`.trim()
|
|
157
|
+
const test = createTest(title, async () => runSteps(child.scenario.steps))
|
|
158
|
+
test.addToSuite(suite)
|
|
159
|
+
test.tags = suite.tags.concat(tags)
|
|
160
|
+
test.file = file
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return suite
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function transformTable(table) {
|
|
168
|
+
let str = ''
|
|
169
|
+
for (const id in table.rows) {
|
|
170
|
+
const cells = table.rows[id].cells
|
|
171
|
+
str += cells
|
|
172
|
+
.map(c => c.value)
|
|
173
|
+
.map(c => c.padEnd(15))
|
|
174
|
+
.join(' | ')
|
|
175
|
+
str += '\n'
|
|
176
|
+
}
|
|
177
|
+
return str
|
|
178
|
+
}
|
|
179
|
+
function addExampleInTable(exampleSteps, placeholders) {
|
|
180
|
+
const steps = JSON.parse(JSON.stringify(exampleSteps))
|
|
181
|
+
for (const placeholder in placeholders) {
|
|
182
|
+
steps.map(step => {
|
|
183
|
+
step = { ...step }
|
|
184
|
+
if (step.dataTable) {
|
|
185
|
+
for (const id in step.dataTable.rows) {
|
|
186
|
+
const cells = step.dataTable.rows[id].cells
|
|
187
|
+
cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return step
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
return steps
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Import translations at module level to avoid async in parser
|
|
197
|
+
let translations = null
|
|
198
|
+
async function loadTranslations() {
|
|
199
|
+
if (!translations) {
|
|
200
|
+
// Import container to ensure it's initialized
|
|
201
|
+
const Container = await import('../container.js')
|
|
202
|
+
await Container.default.started()
|
|
203
|
+
|
|
204
|
+
// Now load translations
|
|
205
|
+
const translationsModule = await import('../../translations/index.js')
|
|
206
|
+
translations = translationsModule.default || translationsModule
|
|
207
|
+
}
|
|
208
|
+
return translations
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getTranslation(language) {
|
|
212
|
+
if (!translations) {
|
|
213
|
+
// Translations not loaded yet, return null (will use default)
|
|
214
|
+
return null
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const translationKeys = Object.keys(translations)
|
|
218
|
+
for (const availableTranslation of translationKeys) {
|
|
219
|
+
if (!language) {
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (availableTranslation.includes(language)) {
|
|
224
|
+
return translations[availableTranslation]
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export { loadTranslations }
|
|
231
|
+
export default gherkinParser
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
import { serializeError } from '../utils.js'
|
|
3
|
+
// const { serializeTest } = require('./test')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a test hook in the testing framework
|
|
7
|
+
* @class
|
|
8
|
+
* @property {Object} suite - The test suite this hook belongs to
|
|
9
|
+
* @property {Object} test - The test object associated with this hook
|
|
10
|
+
* @property {Object} runnable - The current test being executed
|
|
11
|
+
* @property {Object} ctx - The context object
|
|
12
|
+
* @property {Error|null} err - The error that occurred during hook execution, if any
|
|
13
|
+
*/
|
|
14
|
+
class Hook {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new Hook instance
|
|
17
|
+
* @param {Object} context - The context object containing suite and test information
|
|
18
|
+
* @param {Object} context.suite - The test suite
|
|
19
|
+
* @param {Object} context.test - The test object
|
|
20
|
+
* @param {Object} context.ctx - The context object
|
|
21
|
+
* @param {Error} error - The error object if hook execution failed
|
|
22
|
+
*/
|
|
23
|
+
constructor(context, error) {
|
|
24
|
+
this.suite = context.suite
|
|
25
|
+
this.test = context.test
|
|
26
|
+
this.runnable = context?.ctx?.test
|
|
27
|
+
this.ctx = context.ctx
|
|
28
|
+
this.err = error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get hookName() {
|
|
32
|
+
return this.constructor.name.replace('Hook', '')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
simplify() {
|
|
36
|
+
return {
|
|
37
|
+
hookName: this.hookName,
|
|
38
|
+
title: this.title,
|
|
39
|
+
// test: this.test ? serializeTest(this.test) : null,
|
|
40
|
+
// suite: this.suite ? serializeSuite(this.suite) : null,
|
|
41
|
+
error: this.err ? serializeError(this.err) : null,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
toString() {
|
|
46
|
+
return this.hookName
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toCode() {
|
|
50
|
+
return this.toString() + '()'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
retry(n) {
|
|
54
|
+
this.suite.opts[`retry${this.hookName}`] = n
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get title() {
|
|
58
|
+
return this.ctx?.test?.title || this.name
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get name() {
|
|
62
|
+
return this.constructor.name
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class BeforeHook extends Hook {}
|
|
67
|
+
|
|
68
|
+
class AfterHook extends Hook {}
|
|
69
|
+
|
|
70
|
+
class BeforeSuiteHook extends Hook {}
|
|
71
|
+
|
|
72
|
+
class AfterSuiteHook extends Hook {}
|
|
73
|
+
|
|
74
|
+
function fireHook(eventType, suite, error) {
|
|
75
|
+
const hook = suite.ctx?.test?.title?.match(/"([^"]*)"/)[1]
|
|
76
|
+
switch (hook) {
|
|
77
|
+
case 'before each':
|
|
78
|
+
event.emit(eventType, new BeforeHook(suite, error))
|
|
79
|
+
break
|
|
80
|
+
case 'after each':
|
|
81
|
+
event.emit(eventType, new AfterHook(suite, error))
|
|
82
|
+
break
|
|
83
|
+
case 'before all':
|
|
84
|
+
event.emit(eventType, new BeforeSuiteHook(suite, error))
|
|
85
|
+
break
|
|
86
|
+
case 'after all':
|
|
87
|
+
event.emit(eventType, new AfterSuiteHook(suite, error))
|
|
88
|
+
break
|
|
89
|
+
default:
|
|
90
|
+
event.emit(eventType, suite, error)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class HookConfig {
|
|
95
|
+
constructor(hook) {
|
|
96
|
+
this.hook = hook
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
retry(n) {
|
|
100
|
+
this.hook.retry(n)
|
|
101
|
+
return this
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
BeforeHook,
|
|
107
|
+
AfterHook,
|
|
108
|
+
BeforeSuiteHook,
|
|
109
|
+
AfterSuiteHook,
|
|
110
|
+
fireHook,
|
|
111
|
+
HookConfig,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default {
|
|
115
|
+
BeforeHook,
|
|
116
|
+
AfterHook,
|
|
117
|
+
BeforeSuiteHook,
|
|
118
|
+
AfterSuiteHook,
|
|
119
|
+
fireHook,
|
|
120
|
+
HookConfig,
|
|
121
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Suite from 'mocha/lib/suite.js'
|
|
2
|
+
import Test from 'mocha/lib/test.js'
|
|
3
|
+
import { BeforeHook, AfterHook, BeforeSuiteHook, AfterSuiteHook } from './hooks.js'
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
Suite,
|
|
7
|
+
Test,
|
|
8
|
+
BeforeHook,
|
|
9
|
+
AfterHook,
|
|
10
|
+
BeforeSuiteHook,
|
|
11
|
+
AfterSuiteHook,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
Suite,
|
|
16
|
+
Test,
|
|
17
|
+
BeforeHook,
|
|
18
|
+
AfterHook,
|
|
19
|
+
BeforeSuiteHook,
|
|
20
|
+
AfterSuiteHook,
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getParams } from '../parser.js'
|
|
2
|
+
|
|
3
|
+
const getInjectedArguments = async (fn, test, suite) => {
|
|
4
|
+
const containerModule = await import('../container.js')
|
|
5
|
+
const container = containerModule.default || containerModule
|
|
6
|
+
|
|
7
|
+
const testArgs = {}
|
|
8
|
+
const params = getParams(fn) || []
|
|
9
|
+
const objects = container.support()
|
|
10
|
+
|
|
11
|
+
for (const key of params) {
|
|
12
|
+
testArgs[key] = {}
|
|
13
|
+
|
|
14
|
+
// Handle special built-in objects first
|
|
15
|
+
if (key === 'suite') {
|
|
16
|
+
if (test) {
|
|
17
|
+
testArgs[key] = test.parent || test
|
|
18
|
+
} else if (suite) {
|
|
19
|
+
testArgs[key] = suite
|
|
20
|
+
}
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
if (key === 'test') {
|
|
24
|
+
testArgs[key] = test
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (test && test.inject && test.inject[key]) {
|
|
29
|
+
// @FIX: need fix got inject
|
|
30
|
+
testArgs[key] = test.inject[key]
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
if (!objects[key]) {
|
|
34
|
+
throw new Error(`Object of type ${key} is not defined in container`)
|
|
35
|
+
}
|
|
36
|
+
testArgs[key] = container.support(key)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (test) {
|
|
40
|
+
testArgs.suite = test?.parent
|
|
41
|
+
testArgs.test = test
|
|
42
|
+
}
|
|
43
|
+
return testArgs
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { getInjectedArguments }
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { isAsyncFunction } from '../utils.js'
|
|
2
|
+
|
|
1
3
|
/** @class */
|
|
2
4
|
class ScenarioConfig {
|
|
5
|
+
/**
|
|
6
|
+
* @param {CodeceptJS.Test} test
|
|
7
|
+
*/
|
|
3
8
|
constructor(test) {
|
|
4
|
-
this.test = test
|
|
9
|
+
this.test = test
|
|
5
10
|
}
|
|
6
11
|
|
|
7
12
|
/**
|
|
@@ -12,8 +17,8 @@ class ScenarioConfig {
|
|
|
12
17
|
* @returns {this}
|
|
13
18
|
*/
|
|
14
19
|
throws(err) {
|
|
15
|
-
this.test.throws = err
|
|
16
|
-
return this
|
|
20
|
+
this.test.throws = err
|
|
21
|
+
return this
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
/**
|
|
@@ -24,8 +29,8 @@ class ScenarioConfig {
|
|
|
24
29
|
* @returns {this}
|
|
25
30
|
*/
|
|
26
31
|
fails() {
|
|
27
|
-
this.test.throws = new Error()
|
|
28
|
-
return this
|
|
32
|
+
this.test.throws = new Error()
|
|
33
|
+
return this
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -35,9 +40,20 @@ class ScenarioConfig {
|
|
|
35
40
|
* @returns {this}
|
|
36
41
|
*/
|
|
37
42
|
retry(retries) {
|
|
38
|
-
if (process.env.SCENARIO_ONLY) retries = -retries
|
|
39
|
-
this.test.retries(retries)
|
|
40
|
-
return this
|
|
43
|
+
if (process.env.SCENARIO_ONLY) retries = -retries
|
|
44
|
+
this.test.retries(retries)
|
|
45
|
+
return this
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set metadata for this test
|
|
50
|
+
* @param {string} key
|
|
51
|
+
* @param {string} value
|
|
52
|
+
* @returns {this}
|
|
53
|
+
*/
|
|
54
|
+
meta(key, value) {
|
|
55
|
+
this.test.meta[key] = value
|
|
56
|
+
return this
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
/**
|
|
@@ -46,12 +62,12 @@ class ScenarioConfig {
|
|
|
46
62
|
* @returns {this}
|
|
47
63
|
*/
|
|
48
64
|
timeout(timeout) {
|
|
49
|
-
console.log(`Scenario('${this.test.title}', () => {}).timeout(${timeout}) is deprecated!`)
|
|
50
|
-
console.log(`Please use Scenario('${this.test.title}', { timeout: ${timeout / 1000} }, () => {}) instead`)
|
|
51
|
-
console.log('Timeout should be set in seconds')
|
|
65
|
+
console.log(`Scenario('${this.test.title}', () => {}).timeout(${timeout}) is deprecated!`)
|
|
66
|
+
console.log(`Please use Scenario('${this.test.title}', { timeout: ${timeout / 1000} }, () => {}) instead`)
|
|
67
|
+
console.log('Timeout should be set in seconds')
|
|
52
68
|
|
|
53
|
-
this.test.timeout(timeout)
|
|
54
|
-
return this
|
|
69
|
+
this.test.timeout(timeout)
|
|
70
|
+
return this
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
/**
|
|
@@ -60,30 +76,38 @@ class ScenarioConfig {
|
|
|
60
76
|
* @returns {this}
|
|
61
77
|
*/
|
|
62
78
|
inject(obj) {
|
|
63
|
-
this.test.inject = obj
|
|
64
|
-
return this
|
|
79
|
+
this.test.inject = obj
|
|
80
|
+
return this
|
|
65
81
|
}
|
|
66
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @callback ScenarioConfigCallback
|
|
85
|
+
* @param {CodeceptJS.Test} test
|
|
86
|
+
* @returns {Object<string, any>}
|
|
87
|
+
*/
|
|
88
|
+
|
|
67
89
|
/**
|
|
68
90
|
* Configures a helper.
|
|
69
91
|
* Helper name can be omitted and values will be applied to first helper.
|
|
70
|
-
* @param {string | Object<string, any>} helper
|
|
92
|
+
* @param {string | Object<string, any> | ScenarioConfigCallback} helper
|
|
71
93
|
* @param {Object<string, any>} [obj]
|
|
72
94
|
* @returns {this}
|
|
73
95
|
*/
|
|
74
|
-
|
|
96
|
+
config(helper, obj) {
|
|
75
97
|
if (!obj) {
|
|
76
|
-
obj = helper
|
|
77
|
-
helper = 0
|
|
98
|
+
obj = helper
|
|
99
|
+
helper = 0
|
|
78
100
|
}
|
|
79
101
|
if (typeof obj === 'function') {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
if (isAsyncFunction(obj)) {
|
|
103
|
+
obj(this.test).then(res => (this.test.config[helper] = res))
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
106
|
+
obj = obj(this.test)
|
|
84
107
|
}
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
|
|
109
|
+
this.test.config[helper] = obj
|
|
110
|
+
return this
|
|
87
111
|
}
|
|
88
112
|
|
|
89
113
|
/**
|
|
@@ -92,10 +116,10 @@ class ScenarioConfig {
|
|
|
92
116
|
* @returns {this}
|
|
93
117
|
*/
|
|
94
118
|
tag(tagName) {
|
|
95
|
-
if (tagName[0] !== '@') tagName = `@${tagName}
|
|
96
|
-
this.test.tags.push(tagName)
|
|
97
|
-
this.test.title = `${this.test.title.trim()} ${tagName}
|
|
98
|
-
return this
|
|
119
|
+
if (tagName[0] !== '@') tagName = `@${tagName}`
|
|
120
|
+
this.test.tags.push(tagName)
|
|
121
|
+
this.test.title = `${this.test.title.trim()} ${tagName}`
|
|
122
|
+
return this
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
/**
|
|
@@ -104,11 +128,11 @@ class ScenarioConfig {
|
|
|
104
128
|
* @returns {this}
|
|
105
129
|
*/
|
|
106
130
|
injectDependencies(dependencies) {
|
|
107
|
-
Object.keys(dependencies).forEach(
|
|
108
|
-
this.test.inject[key] = dependencies[key]
|
|
109
|
-
})
|
|
110
|
-
return this
|
|
131
|
+
Object.keys(dependencies).forEach(key => {
|
|
132
|
+
this.test.inject[key] = dependencies[key]
|
|
133
|
+
})
|
|
134
|
+
return this
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
137
|
|
|
114
|
-
export default ScenarioConfig
|
|
138
|
+
export default ScenarioConfig
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import MochaSuite from 'mocha/lib/suite.js'
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {import('mocha')} Mocha
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enhances MochaSuite with CodeceptJS specific functionality using composition
|
|
8
|
+
*/
|
|
9
|
+
function enhanceMochaSuite(suite) {
|
|
10
|
+
if (!suite) suite = new MochaSuite('Suite', null, false)
|
|
11
|
+
// already enhanced
|
|
12
|
+
if (suite.codeceptjs) return suite
|
|
13
|
+
|
|
14
|
+
suite.codeceptjs = true
|
|
15
|
+
// Add properties
|
|
16
|
+
suite.tags = suite.title.match(/(\@[a-zA-Z0-9-_]+)/g) || []
|
|
17
|
+
suite.opts = {}
|
|
18
|
+
// suite.totalTimeout = undefined
|
|
19
|
+
|
|
20
|
+
// Override fullTitle method
|
|
21
|
+
suite.fullTitle = () => `${suite.title}:`
|
|
22
|
+
|
|
23
|
+
// Add new methods
|
|
24
|
+
suite.applyOptions = function (opts) {
|
|
25
|
+
if (!opts) opts = {}
|
|
26
|
+
suite.opts = opts
|
|
27
|
+
|
|
28
|
+
if (opts.retries) suite.retries(opts.retries)
|
|
29
|
+
if (opts.timeout) suite.totalTimeout = opts.timeout
|
|
30
|
+
|
|
31
|
+
if (opts.skipInfo && opts.skipInfo.skipped) {
|
|
32
|
+
suite.pending = true
|
|
33
|
+
suite.opts = { ...this.opts, skipInfo: opts.skipInfo }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
suite.simplify = function () {
|
|
38
|
+
return serializeSuite(this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return suite
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Factory function to create enhanced suites
|
|
46
|
+
* @param {Mocha.Suite} parent - Parent suite
|
|
47
|
+
* @param {string} title - Suite title
|
|
48
|
+
* @returns {CodeceptJS.Suite & Mocha.Suite} New enhanced suite instance
|
|
49
|
+
*/
|
|
50
|
+
function createSuite(parent, title) {
|
|
51
|
+
const suite = MochaSuite.create(parent, title)
|
|
52
|
+
suite.timeout(0)
|
|
53
|
+
return enhanceMochaSuite(suite)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function serializeSuite(suite) {
|
|
57
|
+
suite = { ...suite }
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
opts: suite.opts || {},
|
|
61
|
+
tags: suite.tags || [],
|
|
62
|
+
retries: suite._retries,
|
|
63
|
+
title: suite.title,
|
|
64
|
+
status: suite.status,
|
|
65
|
+
notes: suite.notes || [],
|
|
66
|
+
meta: suite.meta || {},
|
|
67
|
+
duration: suite.duration || 0,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function deserializeSuite(suite) {
|
|
72
|
+
suite = Object.assign(new MochaSuite(suite.title), suite)
|
|
73
|
+
enhanceMochaSuite(suite)
|
|
74
|
+
return suite
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
createSuite,
|
|
79
|
+
enhanceMochaSuite,
|
|
80
|
+
serializeSuite,
|
|
81
|
+
deserializeSuite,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default {
|
|
85
|
+
createSuite,
|
|
86
|
+
enhanceMochaSuite,
|
|
87
|
+
serializeSuite,
|
|
88
|
+
deserializeSuite,
|
|
89
|
+
}
|