codeceptjs 4.0.1-beta.9 → 4.0.1
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 +39 -28
- package/bin/codecept.js +17 -4
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +1189 -0
- package/docs/advanced.md +201 -0
- package/docs/agents.md +181 -0
- package/docs/ai.md +489 -0
- package/docs/aitrace.md +266 -0
- package/docs/api.md +332 -0
- package/docs/architecture.md +235 -0
- package/docs/assertions.md +415 -0
- package/docs/auth.md +318 -0
- package/docs/basics.md +424 -0
- package/docs/bdd.md +539 -0
- package/docs/best.md +240 -0
- package/docs/bootstrap.md +132 -0
- package/docs/commands.md +352 -0
- package/docs/community-helpers.md +63 -0
- package/docs/configuration.md +185 -0
- package/docs/continuous-integration.md +431 -0
- package/docs/custom-helpers.md +297 -0
- package/docs/data.md +448 -0
- package/docs/debugging.md +332 -0
- package/docs/detox.md +235 -0
- package/docs/docker.md +107 -0
- package/docs/effects.md +179 -0
- package/docs/element-based-testing.md +295 -0
- package/docs/element-selection.md +125 -0
- package/docs/els.md +328 -0
- package/docs/environment-variables.md +131 -0
- package/docs/examples.md +160 -0
- package/docs/heal.md +213 -0
- package/docs/helpers/ApiDataFactory.md +267 -0
- package/docs/helpers/Appium.md +1419 -0
- package/docs/helpers/Detox.md +665 -0
- package/docs/helpers/ExpectHelper.md +275 -0
- package/docs/helpers/FileSystem.md +152 -0
- package/docs/helpers/GraphQL.md +152 -0
- package/docs/helpers/GraphQLDataFactory.md +226 -0
- package/docs/helpers/JSONResponse.md +255 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/Playwright.md +2970 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2583 -0
- package/docs/helpers/REST.md +289 -0
- package/docs/helpers/WebDriver.md +2639 -0
- package/docs/hooks.md +148 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +121 -0
- package/docs/internal-test-server.md +89 -0
- package/docs/locators.md +355 -0
- package/docs/mcp.md +485 -0
- package/docs/migrate-from-cypress.md +98 -0
- package/docs/migrate-from-java.md +108 -0
- package/docs/migrate-from-protractor.md +101 -0
- package/docs/migrate-from-testcafe.md +99 -0
- package/docs/migration-4.md +745 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +399 -0
- package/docs/parallel.md +187 -0
- package/docs/playwright.md +714 -0
- package/docs/plugins/aiTrace.md +49 -0
- package/docs/plugins/analyze.md +66 -0
- package/docs/plugins/auth.md +241 -0
- package/docs/plugins/autoDelay.md +48 -0
- package/docs/plugins/browser.md +41 -0
- package/docs/plugins/coverage.md +39 -0
- package/docs/plugins/customLocator.md +119 -0
- package/docs/plugins/customReporter.md +16 -0
- package/docs/plugins/expose.md +75 -0
- package/docs/plugins/heal.md +44 -0
- package/docs/plugins/junitReporter.md +51 -0
- package/docs/plugins/pageInfo.md +34 -0
- package/docs/plugins/pause.md +43 -0
- package/docs/plugins/pauseOnFail.md +18 -0
- package/docs/plugins/retryFailedStep.md +75 -0
- package/docs/plugins/screencast.md +55 -0
- package/docs/plugins/screenshot.md +58 -0
- package/docs/plugins/screenshotOnFail.md +18 -0
- package/docs/plugins/stepTimeout.md +65 -0
- package/docs/plugins.md +87 -0
- package/docs/puppeteer.md +314 -0
- package/docs/quickstart.md +120 -0
- package/docs/reports.md +195 -0
- package/docs/retry.md +311 -0
- package/docs/secrets.md +150 -0
- package/docs/sessions.md +80 -0
- package/docs/shadow.md +68 -0
- package/docs/store.md +94 -0
- package/docs/test-structure.md +275 -0
- package/docs/timeouts.md +183 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +323 -0
- package/docs/typescript.md +159 -0
- package/docs/web-element.md +251 -0
- package/docs/webdriver.md +641 -0
- package/docs/within.md +55 -0
- package/lib/actor.js +1 -36
- package/lib/ai.js +3 -2
- package/lib/aria.js +260 -0
- package/lib/assertions.js +18 -0
- package/lib/codecept.js +34 -25
- package/lib/command/check.js +2 -1
- package/lib/command/definitions.js +14 -10
- package/lib/command/dryRun.js +24 -5
- package/lib/command/generate.js +3 -1
- package/lib/command/gherkin/snippets.js +5 -4
- package/lib/command/init.js +249 -270
- package/lib/command/list.js +150 -10
- package/lib/command/query.js +218 -0
- package/lib/command/run-multiple.js +3 -2
- package/lib/command/run-workers.js +14 -16
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +117 -9
- package/lib/config.js +98 -19
- package/lib/container.js +188 -19
- package/lib/effects.js +17 -0
- package/lib/element/WebElement.js +246 -2
- package/lib/els.js +12 -6
- package/lib/globals.js +32 -19
- package/lib/heal.js +7 -4
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/Appium.js +8 -8
- package/lib/helper/FileSystem.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +2 -1
- package/lib/helper/Playwright.js +367 -516
- package/lib/helper/Puppeteer.js +343 -197
- package/lib/helper/WebDriver.js +324 -111
- package/lib/helper/errors/ElementNotFound.js +5 -2
- package/lib/helper/errors/MultipleElementsFound.js +52 -0
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/Download.js +45 -0
- package/lib/helper/extras/PlaywrightLocator.js +7 -107
- package/lib/helper/extras/elementSelection.js +58 -0
- package/lib/helper/extras/focusCheck.js +43 -0
- package/lib/helper/extras/richTextEditor.js +178 -0
- package/lib/helper/scripts/dropFile.js +11 -0
- package/lib/history.js +3 -2
- package/lib/html.js +103 -16
- package/lib/index.js +9 -1
- package/lib/listener/config.js +6 -4
- package/lib/listener/emptyRun.js +2 -1
- package/lib/listener/globalRetry.js +32 -6
- package/lib/listener/helpers.js +6 -15
- package/lib/listener/mocha.js +2 -1
- package/lib/listener/pageobjects.js +43 -0
- package/lib/listener/result.js +3 -2
- package/lib/locator.js +158 -16
- package/lib/mocha/cli.js +19 -1
- package/lib/mocha/factory.js +13 -28
- package/lib/mocha/inject.js +1 -1
- package/lib/mocha/scenarioConfig.js +2 -1
- package/lib/mocha/test.js +4 -2
- package/lib/mocha/ui.js +5 -6
- package/lib/output.js +2 -2
- package/lib/parser.js +2 -2
- package/lib/pause.js +38 -4
- package/lib/plugin/aiTrace.js +457 -0
- package/lib/plugin/analyze.js +9 -9
- package/lib/plugin/auth.js +5 -4
- package/lib/plugin/browser.js +77 -0
- package/lib/plugin/expose.js +159 -0
- package/lib/plugin/heal.js +47 -3
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/pageInfo.js +54 -52
- package/lib/plugin/pause.js +131 -0
- package/lib/plugin/pauseOnFail.js +11 -33
- package/lib/plugin/retryFailedStep.js +43 -32
- package/lib/plugin/screencast.js +289 -0
- package/lib/plugin/screenshot.js +558 -0
- package/lib/plugin/screenshotOnFail.js +9 -170
- package/lib/plugin/stepTimeout.js +3 -2
- package/lib/recorder.js +1 -1
- package/lib/rerun.js +2 -1
- package/lib/result.js +2 -1
- package/lib/step/base.js +23 -9
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +15 -2
- package/lib/step/helper.js +4 -4
- package/lib/step/meta.js +3 -3
- package/lib/step/record.js +12 -4
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- package/lib/utils/loaderCheck.js +41 -3
- package/lib/utils/mask_data.js +2 -1
- package/lib/utils/pluginParser.js +151 -0
- package/lib/utils/trace.js +297 -0
- package/lib/utils/typescript.js +261 -49
- package/lib/utils.js +77 -3
- package/lib/workers.js +123 -17
- package/package.json +48 -43
- package/typings/index.d.ts +120 -9
- package/typings/promiseBasedTypes.d.ts +3243 -6057
- package/typings/types.d.ts +3541 -6506
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -11
- package/docs/webapi/attachFile.mustache +0 -12
- package/docs/webapi/blur.mustache +0 -18
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -9
- package/docs/webapi/clearField.mustache +0 -9
- package/docs/webapi/click.mustache +0 -29
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/dontSeeTraffic.mustache +0 -13
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -16
- package/docs/webapi/flushNetworkTraffics.mustache +0 -5
- package/docs/webapi/focus.mustache +0 -13
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/grabWebElement.mustache +0 -9
- package/docs/webapi/grabWebElements.mustache +0 -9
- package/docs/webapi/moveCursorTo.mustache +0 -12
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/seeTraffic.mustache +0 -36
- package/docs/webapi/selectOption.mustache +0 -21
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/startRecordingTraffic.mustache +0 -8
- package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
- package/docs/webapi/stopRecordingTraffic.mustache +0 -5
- package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -21
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForCookie.mustache +0 -9
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForDisabled.mustache +0 -6
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/lib/helper/AI.js +0 -214
- package/lib/helper/Mochawesome.js +0 -96
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
- package/lib/helper/extras/React.js +0 -65
- package/lib/listener/enhancedGlobalRetry.js +0 -110
- package/lib/plugin/enhancedRetryFailedStep.js +0 -99
- package/lib/plugin/htmlReporter.js +0 -3648
- package/lib/plugin/stepByStepReport.js +0 -427
- package/lib/plugin/subtitles.js +0 -89
- package/lib/retryCoordinator.js +0 -207
|
@@ -1,178 +1,17 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
import Container from '../container.js'
|
|
5
|
-
|
|
6
|
-
import recorder from '../recorder.js'
|
|
7
|
-
|
|
8
|
-
import event from '../event.js'
|
|
9
|
-
|
|
10
1
|
import output from '../output.js'
|
|
2
|
+
import screenshot from './screenshot.js'
|
|
11
3
|
|
|
12
|
-
|
|
13
|
-
import Codeceptjs from '../index.js'
|
|
14
|
-
import { testToFileName } from '../mocha/test.js'
|
|
15
|
-
|
|
16
|
-
const defaultConfig = {
|
|
17
|
-
uniqueScreenshotNames: false,
|
|
18
|
-
disableScreenshots: false,
|
|
19
|
-
fullPageScreenshots: false,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
4
|
+
let warned = false
|
|
23
5
|
|
|
24
6
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Initially this functionality was part of corresponding helper but has been moved into plugin since 1.4
|
|
28
|
-
*
|
|
29
|
-
* This plugin is **enabled by default**.
|
|
30
|
-
*
|
|
31
|
-
* #### Configuration
|
|
32
|
-
*
|
|
33
|
-
* Configuration can either be taken from a corresponding helper (deprecated) or a from plugin config (recommended).
|
|
34
|
-
*
|
|
35
|
-
* ```js
|
|
36
|
-
* plugins: {
|
|
37
|
-
* screenshotOnFail: {
|
|
38
|
-
* enabled: true
|
|
39
|
-
* }
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* Possible config options:
|
|
44
|
-
*
|
|
45
|
-
* * `uniqueScreenshotNames`: use unique names for screenshot. Default: false.
|
|
46
|
-
* * `fullPageScreenshots`: make full page screenshots. Default: false.
|
|
47
|
-
*
|
|
7
|
+
* Saves a screenshot when a test fails.
|
|
48
8
|
*
|
|
9
|
+
* **Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.
|
|
49
10
|
*/
|
|
50
|
-
export default function (config) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
for (const helperName of supportedHelpers) {
|
|
55
|
-
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
56
|
-
helper = helpers[helperName]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!helper) return // no helpers for screenshot
|
|
61
|
-
|
|
62
|
-
const options = Object.assign(defaultConfig, helper.options, config)
|
|
63
|
-
|
|
64
|
-
if (helpers.Mochawesome) {
|
|
65
|
-
if (helpers.Mochawesome.config) {
|
|
66
|
-
options.uniqueScreenshotNames = helpers.Mochawesome.config.uniqueScreenshotNames
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (Codeceptjs.container.mocha()) {
|
|
71
|
-
options.reportDir = Codeceptjs.container.mocha()?.options?.reporterOptions && Codeceptjs.container.mocha()?.options?.reporterOptions?.reportDir
|
|
11
|
+
export default function (config = {}) {
|
|
12
|
+
if (!warned) {
|
|
13
|
+
output.error('screenshotOnFail is deprecated; use the `screenshot` plugin (default on=fail).')
|
|
14
|
+
warned = true
|
|
72
15
|
}
|
|
73
|
-
|
|
74
|
-
if (options.disableScreenshots) {
|
|
75
|
-
// old version of disabling screenshots
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
|
|
80
|
-
if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
|
|
81
|
-
// no browser here
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
recorder.add(
|
|
86
|
-
'screenshot of failed test',
|
|
87
|
-
async () => {
|
|
88
|
-
const dataType = 'image/png'
|
|
89
|
-
// This prevents data driven to be included in the failed screenshot file name
|
|
90
|
-
let fileName
|
|
91
|
-
|
|
92
|
-
if (options.uniqueScreenshotNames && test) {
|
|
93
|
-
fileName = `${testToFileName(test, { suffix: '', unique: true })}.failed.png`
|
|
94
|
-
} else {
|
|
95
|
-
fileName = `${testToFileName(test, { suffix: '', unique: false })}.failed.png`
|
|
96
|
-
}
|
|
97
|
-
const quietMode = !('output_dir' in global) || !global.output_dir
|
|
98
|
-
if (!quietMode) {
|
|
99
|
-
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Re-check helpers at runtime in case they weren't ready during plugin init
|
|
103
|
-
const runtimeHelpers = Container.helpers()
|
|
104
|
-
let runtimeHelper = null
|
|
105
|
-
for (const helperName of supportedHelpers) {
|
|
106
|
-
if (Object.keys(runtimeHelpers).indexOf(helperName) > -1) {
|
|
107
|
-
runtimeHelper = runtimeHelpers[helperName]
|
|
108
|
-
break
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (runtimeHelper && typeof runtimeHelper.saveScreenshot === 'function') {
|
|
113
|
-
helper = runtimeHelper
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
if (options.reportDir) {
|
|
118
|
-
fileName = path.join(options.reportDir, fileName)
|
|
119
|
-
const mochaReportDir = path.resolve(process.cwd(), options.reportDir)
|
|
120
|
-
if (!fileExists(mochaReportDir)) {
|
|
121
|
-
fs.mkdirSync(mochaReportDir)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check if browser/page is still available before attempting screenshot
|
|
126
|
-
if (helper.page && helper.page.isClosed && helper.page.isClosed()) {
|
|
127
|
-
throw new Error('Browser page has been closed')
|
|
128
|
-
}
|
|
129
|
-
if (helper.browser && helper.browser.isConnected && !helper.browser.isConnected()) {
|
|
130
|
-
throw new Error('Browser has been disconnected')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Add timeout wrapper to prevent hanging with shorter timeout for ESM
|
|
134
|
-
const screenshotPromise = helper.saveScreenshot(fileName, options.fullPageScreenshots)
|
|
135
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
136
|
-
setTimeout(() => reject(new Error('Screenshot timeout after 5 seconds')), 5000)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
await Promise.race([screenshotPromise, timeoutPromise])
|
|
140
|
-
|
|
141
|
-
if (!test.artifacts) test.artifacts = {}
|
|
142
|
-
// Some unit tests may not define global.output_dir; avoid throwing when it is undefined
|
|
143
|
-
// Detect output directory safely (may not be initialized in narrow unit tests)
|
|
144
|
-
const baseOutputDir = 'output_dir' in global && typeof global.output_dir === 'string' && global.output_dir ? global.output_dir : null
|
|
145
|
-
if (baseOutputDir) {
|
|
146
|
-
test.artifacts.screenshot = path.join(baseOutputDir, fileName)
|
|
147
|
-
if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
|
|
148
|
-
test.attachments = [path.join(baseOutputDir, fileName)]
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
// Fallback: just store the file name to keep tests stable without triggering path errors
|
|
152
|
-
test.artifacts.screenshot = fileName
|
|
153
|
-
}
|
|
154
|
-
} catch (err) {
|
|
155
|
-
if (!quietMode) {
|
|
156
|
-
output.plugin('screenshotOnFail', `Failed to save screenshot: ${err.message}`)
|
|
157
|
-
}
|
|
158
|
-
// Enhanced error handling for browser closed scenarios
|
|
159
|
-
if (
|
|
160
|
-
err &&
|
|
161
|
-
((err.message &&
|
|
162
|
-
(err.message.includes('Target page, context or browser has been closed') ||
|
|
163
|
-
err.message.includes('Browser page has been closed') ||
|
|
164
|
-
err.message.includes('Browser has been disconnected') ||
|
|
165
|
-
err.message.includes('was terminated due to') ||
|
|
166
|
-
err.message.includes('no such window: target window already closed') ||
|
|
167
|
-
err.message.includes('Screenshot timeout after'))) ||
|
|
168
|
-
(err.type && err.type === 'RuntimeError'))
|
|
169
|
-
) {
|
|
170
|
-
output.log(`Can't make screenshot, ${err.message}`)
|
|
171
|
-
helper.isRunning = false
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
true,
|
|
176
|
-
)
|
|
177
|
-
})
|
|
16
|
+
return screenshot({ ...config, on: 'fail' })
|
|
178
17
|
}
|
|
@@ -32,7 +32,7 @@ const defaultConfig = {
|
|
|
32
32
|
* #### Configuration:
|
|
33
33
|
*
|
|
34
34
|
* * `timeout` - global step timeout, default 150 seconds
|
|
35
|
-
* * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.
|
|
35
|
+
* * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false
|
|
36
36
|
* * `noTimeoutSteps` - an array of steps with no timeout. Default:
|
|
37
37
|
* * `amOnPage`
|
|
38
38
|
* * `wait*`
|
|
@@ -68,6 +68,7 @@ export default function(config) {
|
|
|
68
68
|
config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
|
|
69
69
|
|
|
70
70
|
event.dispatcher.on(event.step.before, step => {
|
|
71
|
+
if (!step.title) return
|
|
71
72
|
let stepTimeout
|
|
72
73
|
for (let stepRule of config.customTimeoutSteps) {
|
|
73
74
|
let customTimeout = 0
|
|
@@ -75,7 +76,7 @@ export default function(config) {
|
|
|
75
76
|
if (stepRule.length > 1) customTimeout = stepRule[1]
|
|
76
77
|
stepRule = stepRule[0]
|
|
77
78
|
}
|
|
78
|
-
if (stepRule instanceof RegExp ? step.
|
|
79
|
+
if (stepRule instanceof RegExp ? step.title.match(stepRule) : step.title === stepRule || (stepRule.indexOf('*') && step.title.startsWith(stepRule.slice(0, -1)))) {
|
|
79
80
|
stepTimeout = customTimeout
|
|
80
81
|
break
|
|
81
82
|
}
|
package/lib/recorder.js
CHANGED
|
@@ -217,7 +217,7 @@ export default {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
const retryRules = this.retries.slice().reverse()
|
|
220
|
-
return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
|
|
220
|
+
return promiseRetry(Object.assign({}, defaultRetryOptions, retryOpts), (retry, number) => {
|
|
221
221
|
if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
|
|
222
222
|
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
223
223
|
return Promise.race([promise, Promise.resolve(res).then(fn)])
|
package/lib/rerun.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fsPath from 'path'
|
|
2
|
+
import store from './store.js'
|
|
2
3
|
import container from './container.js'
|
|
3
4
|
import event from './event.js'
|
|
4
5
|
import BaseCodecept from './codecept.js'
|
|
@@ -29,7 +30,7 @@ class CodeceptRerunner extends BaseCodecept {
|
|
|
29
30
|
let filesToRun = this.testFiles
|
|
30
31
|
if (test) {
|
|
31
32
|
if (!fsPath.isAbsolute(test)) {
|
|
32
|
-
test = fsPath.join(
|
|
33
|
+
test = fsPath.join(store.codeceptDir, test)
|
|
33
34
|
}
|
|
34
35
|
filesToRun = this.testFiles.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
35
36
|
}
|
package/lib/result.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { serializeTest } from './mocha/test.js'
|
|
4
|
+
import store from './store.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @typedef {Object} Stats Statistics for a test result.
|
|
@@ -212,7 +213,7 @@ class Result {
|
|
|
212
213
|
*/
|
|
213
214
|
save(fileName) {
|
|
214
215
|
if (!fileName) fileName = 'result.json'
|
|
215
|
-
fs.writeFileSync(path.join(
|
|
216
|
+
fs.writeFileSync(path.join(store.outputDir, fileName), JSON.stringify(this.simplify(), null, 2))
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/**
|
package/lib/step/base.js
CHANGED
|
@@ -3,18 +3,19 @@ import Secret from '../secret.js'
|
|
|
3
3
|
import { getCurrentTimeout } from '../timeout.js'
|
|
4
4
|
import { ucfirst, humanizeString, serializeError } from '../utils.js'
|
|
5
5
|
import recordStep from './record.js'
|
|
6
|
+
import store from '../store.js'
|
|
6
7
|
|
|
7
8
|
const STACK_LINE = 5
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Each command in test executed through `I.` object is wrapped in Step.
|
|
11
12
|
* Step allows logging executed commands and triggers hook before and after step execution.
|
|
12
|
-
* @param {string}
|
|
13
|
+
* @param {string} title
|
|
13
14
|
*/
|
|
14
15
|
class Step {
|
|
15
|
-
constructor(
|
|
16
|
+
constructor(title) {
|
|
16
17
|
/** @member {string} */
|
|
17
|
-
this.
|
|
18
|
+
this.title = title
|
|
18
19
|
/** @member {Map<number, number>} */
|
|
19
20
|
this.timeouts = new Map()
|
|
20
21
|
|
|
@@ -42,7 +43,7 @@ class Step {
|
|
|
42
43
|
/** @member {any} */
|
|
43
44
|
this.helper = null
|
|
44
45
|
/** @member {string} */
|
|
45
|
-
this.helperMethod =
|
|
46
|
+
this.helperMethod = title
|
|
46
47
|
|
|
47
48
|
this.startTime = 0
|
|
48
49
|
this.endTime = 0
|
|
@@ -102,7 +103,7 @@ class Step {
|
|
|
102
103
|
|
|
103
104
|
/** @return {string} */
|
|
104
105
|
humanize() {
|
|
105
|
-
return humanizeString(this.
|
|
106
|
+
return humanizeString(this.title)
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** @return {string} */
|
|
@@ -147,9 +148,22 @@ class Step {
|
|
|
147
148
|
line() {
|
|
148
149
|
const lines = this.stack.split('\n')
|
|
149
150
|
if (lines[STACK_LINE]) {
|
|
150
|
-
|
|
151
|
-
.replace(
|
|
151
|
+
let line = lines[STACK_LINE].trim()
|
|
152
|
+
.replace(store.codeceptDir || '', '.')
|
|
152
153
|
.trim()
|
|
154
|
+
|
|
155
|
+
// Map .temp.mjs back to original .ts files using container's tsFileMapping
|
|
156
|
+
const fileMapping = store.tsFileMapping
|
|
157
|
+
if (line.includes('.temp.mjs') && fileMapping) {
|
|
158
|
+
for (const [tsFile, mjsFile] of fileMapping.entries()) {
|
|
159
|
+
if (line.includes(mjsFile)) {
|
|
160
|
+
line = line.replace(mjsFile, tsFile)
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return line
|
|
153
167
|
}
|
|
154
168
|
return ''
|
|
155
169
|
}
|
|
@@ -166,7 +180,7 @@ class Step {
|
|
|
166
180
|
|
|
167
181
|
/** @return {string} */
|
|
168
182
|
toCode() {
|
|
169
|
-
return `${this.prefix}${this.actor}.${this.
|
|
183
|
+
return `${this.prefix}${this.actor}.${this.title}(${this.humanizeArgs()})${this.suffix}`
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
isMetaStep() {
|
|
@@ -209,7 +223,7 @@ class Step {
|
|
|
209
223
|
|
|
210
224
|
return {
|
|
211
225
|
opts: step.opts || {},
|
|
212
|
-
title: step.
|
|
226
|
+
title: step.title,
|
|
213
227
|
args: args,
|
|
214
228
|
status: step.status,
|
|
215
229
|
startTime: step.startTime,
|
package/lib/step/comment.js
CHANGED
package/lib/step/config.js
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} StepOptions
|
|
3
|
+
* @property {number|'first'|'last'} [elementIndex] - Select a specific element when multiple match. 1-based positive index, negative from end, or 'first'/'last'.
|
|
4
|
+
* @property {boolean} [exact] - Enable strict mode for this step. Throws if multiple elements match.
|
|
5
|
+
* @property {boolean} [strictMode] - Alias for exact.
|
|
6
|
+
* @property {boolean} [ignoreCase] - Perform case-insensitive text matching.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* StepConfig is a configuration object for a step.
|
|
3
11
|
* It is used to create a new step that is a combination of other steps.
|
|
4
12
|
*/
|
|
5
13
|
class StepConfig {
|
|
6
14
|
constructor(opts = {}) {
|
|
7
|
-
/** @member {{ opts:
|
|
15
|
+
/** @member {{ opts: StepOptions, timeout: number|undefined, retry: number|undefined }} */
|
|
8
16
|
this.config = {
|
|
9
17
|
opts,
|
|
10
18
|
timeout: undefined,
|
|
11
19
|
retry: undefined,
|
|
12
20
|
}
|
|
21
|
+
this.__isStepConfig = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static isStepConfig(obj) {
|
|
25
|
+
return obj && (obj instanceof StepConfig || obj.__isStepConfig === true)
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
/**
|
|
16
29
|
* Set the options for the step.
|
|
17
|
-
* @param {
|
|
30
|
+
* @param {StepOptions} opts - The options for the step.
|
|
18
31
|
* @returns {StepConfig} - The step configuration object.
|
|
19
32
|
*/
|
|
20
33
|
opts(opts) {
|
package/lib/step/helper.js
CHANGED
|
@@ -2,12 +2,12 @@ import Step from './base.js'
|
|
|
2
2
|
import store from '../store.js'
|
|
3
3
|
|
|
4
4
|
class HelperStep extends Step {
|
|
5
|
-
constructor(helper,
|
|
6
|
-
super(
|
|
5
|
+
constructor(helper, title) {
|
|
6
|
+
super(title)
|
|
7
7
|
/** @member {CodeceptJS.Helper} helper corresponding helper */
|
|
8
8
|
this.helper = helper
|
|
9
|
-
/** @member {string} helperMethod
|
|
10
|
-
this.helperMethod =
|
|
9
|
+
/** @member {string} helperMethod title of method to be executed */
|
|
10
|
+
this.helperMethod = title
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
package/lib/step/meta.js
CHANGED
|
@@ -29,7 +29,7 @@ class MetaStep extends Step {
|
|
|
29
29
|
const actorText = this.actor
|
|
30
30
|
|
|
31
31
|
if (this.isBDD()) {
|
|
32
|
-
return `${this.prefix}${actorText} ${this.
|
|
32
|
+
return `${this.prefix}${actorText} ${this.title} "${this.humanizeArgs()}${this.suffix}"`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (actorText === 'I') {
|
|
@@ -37,14 +37,14 @@ class MetaStep extends Step {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (!this.actor) {
|
|
40
|
-
return `${this.
|
|
40
|
+
return `${this.title} ${this.humanizeArgs()}${this.suffix}`.trim()
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`.trim()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
humanize() {
|
|
47
|
-
return humanizeString(this.
|
|
47
|
+
return humanizeString(this.title)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
setTrace() {}
|
package/lib/step/record.js
CHANGED
|
@@ -5,22 +5,23 @@ import output from '../output.js'
|
|
|
5
5
|
import store from '../store.js'
|
|
6
6
|
import { TIMEOUT_ORDER } from '../timeout.js'
|
|
7
7
|
import retryStep from './retry.js'
|
|
8
|
+
import { fixErrorStack } from '../utils/typescript.js'
|
|
8
9
|
function recordStep(step, args) {
|
|
9
10
|
step.status = 'queued'
|
|
10
11
|
|
|
11
12
|
// apply step configuration
|
|
12
13
|
const lastArg = args[args.length - 1]
|
|
13
|
-
if (lastArg
|
|
14
|
+
if (StepConfig.isStepConfig(lastArg)) {
|
|
14
15
|
const stepConfig = args.pop()
|
|
15
16
|
const { opts, timeout, retry } = stepConfig.getConfig()
|
|
16
17
|
|
|
17
18
|
if (opts) {
|
|
18
|
-
output.debug(`Step ${step.
|
|
19
|
+
output.debug(`Step ${step.title}: options applied ${JSON.stringify(opts)}`)
|
|
19
20
|
store.stepOptions = opts
|
|
20
21
|
step.opts = opts
|
|
21
22
|
}
|
|
22
23
|
if (timeout) {
|
|
23
|
-
output.debug(`Step ${step.
|
|
24
|
+
output.debug(`Step ${step.title} timeout ${timeout}s`)
|
|
24
25
|
step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
|
|
25
26
|
}
|
|
26
27
|
if (retry) retryStep(retry)
|
|
@@ -30,7 +31,7 @@ function recordStep(step, args) {
|
|
|
30
31
|
// run async before step hooks
|
|
31
32
|
event.emit(event.step.before, step)
|
|
32
33
|
|
|
33
|
-
const task = `${step.
|
|
34
|
+
const task = `${step.title}: ${step.humanizeArgs()}`
|
|
34
35
|
let val
|
|
35
36
|
|
|
36
37
|
// run step inside promise
|
|
@@ -60,6 +61,13 @@ function recordStep(step, args) {
|
|
|
60
61
|
recorder.catch(err => {
|
|
61
62
|
step.status = 'failed'
|
|
62
63
|
step.endTime = +Date.now()
|
|
64
|
+
|
|
65
|
+
// Fix error stack to point to original .ts files (lazy import to avoid circular dependency)
|
|
66
|
+
const fileMapping = store.tsFileMapping
|
|
67
|
+
if (fileMapping) {
|
|
68
|
+
fixErrorStack(err, fileMapping)
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
event.emit(event.step.failed, step, err)
|
|
64
72
|
event.emit(event.step.finished, step)
|
|
65
73
|
throw err
|
package/lib/store.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Global store for current session
|
|
3
3
|
* @namespace
|
|
4
4
|
*/
|
|
5
5
|
const store = {
|
|
6
|
+
// --- Required (set once via initialize(), immutable after) ---
|
|
7
|
+
|
|
8
|
+
/** @type {string | null} */
|
|
9
|
+
_codeceptDir: null,
|
|
10
|
+
/** @type {string | null} */
|
|
11
|
+
_outputDir: null,
|
|
12
|
+
|
|
13
|
+
get codeceptDir() {
|
|
14
|
+
return this._codeceptDir || global.codecept_dir || null
|
|
15
|
+
},
|
|
16
|
+
set codeceptDir(val) {
|
|
17
|
+
this._codeceptDir = val
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
get outputDir() {
|
|
21
|
+
return this._outputDir || global.output_dir || null
|
|
22
|
+
},
|
|
23
|
+
set outputDir(val) {
|
|
24
|
+
this._outputDir = val
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
workerMode: false,
|
|
29
|
+
|
|
30
|
+
// --- Session config (per-session, mutable, set at session start) ---
|
|
31
|
+
|
|
6
32
|
/**
|
|
7
33
|
* If we are in --debug mode
|
|
8
34
|
* @type {boolean}
|
|
@@ -27,20 +53,63 @@ const store = {
|
|
|
27
53
|
* @type {boolean}
|
|
28
54
|
*/
|
|
29
55
|
dryRun: false,
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Feature.only() was used
|
|
59
|
+
* @type {boolean}
|
|
60
|
+
*/
|
|
61
|
+
featureOnly: false,
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Scenario.only() was used
|
|
65
|
+
* @type {boolean}
|
|
66
|
+
*/
|
|
67
|
+
scenarioOnly: false,
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Mask sensitive data config
|
|
71
|
+
* @type {boolean|object}
|
|
72
|
+
*/
|
|
73
|
+
maskSensitiveData: false,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* noGlobals mode — user imports everything
|
|
77
|
+
* @type {boolean}
|
|
78
|
+
*/
|
|
79
|
+
noGlobals: false,
|
|
80
|
+
|
|
81
|
+
// --- State (tracks current execution, changes constantly) ---
|
|
82
|
+
|
|
30
83
|
/**
|
|
31
84
|
* If we are in pause mode
|
|
32
85
|
* @type {boolean}
|
|
33
86
|
*/
|
|
34
87
|
onPause: false,
|
|
35
88
|
|
|
36
|
-
// current object states
|
|
37
|
-
|
|
38
89
|
/** @type {CodeceptJS.Test | null} */
|
|
39
90
|
currentTest: null,
|
|
40
91
|
/** @type {CodeceptJS.Step | null} */
|
|
41
92
|
currentStep: null,
|
|
42
93
|
/** @type {CodeceptJS.Suite | null} */
|
|
43
94
|
currentSuite: null,
|
|
95
|
+
|
|
96
|
+
/** @type {Map<string, string> | null} */
|
|
97
|
+
tsFileMapping: null,
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Initialize required store fields.
|
|
101
|
+
* These values cannot be overwritten after initialization.
|
|
102
|
+
* @param {object} opts
|
|
103
|
+
* @param {string} opts.codeceptDir - root directory of tests
|
|
104
|
+
* @param {string} opts.outputDir - resolved output directory
|
|
105
|
+
*/
|
|
106
|
+
initialize(opts) {
|
|
107
|
+
if (!opts.codeceptDir) throw new Error('codeceptDir is required')
|
|
108
|
+
if (!opts.outputDir) throw new Error('outputDir is required')
|
|
109
|
+
|
|
110
|
+
this._codeceptDir = opts.codeceptDir
|
|
111
|
+
this._outputDir = opts.outputDir
|
|
112
|
+
},
|
|
44
113
|
}
|
|
45
114
|
|
|
46
115
|
export default store
|
package/lib/translation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import merge from 'lodash.merge'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { createRequire } from 'module'
|
|
4
|
+
import store from './store.js'
|
|
4
5
|
|
|
5
6
|
const defaultVocabulary = {
|
|
6
7
|
I: 'I',
|
|
@@ -15,7 +16,7 @@ class Translation {
|
|
|
15
16
|
|
|
16
17
|
loadVocabulary(vocabularyFile) {
|
|
17
18
|
if (!vocabularyFile) return
|
|
18
|
-
const filePath = path.join(
|
|
19
|
+
const filePath = path.join(store.codeceptDir, vocabularyFile)
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
22
|
const require = createRequire(import.meta.url)
|
package/lib/utils/loaderCheck.js
CHANGED
|
@@ -65,9 +65,18 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
|
|
|
65
65
|
✅ Complete: Handles all TypeScript features
|
|
66
66
|
|
|
67
67
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
68
|
-
│ Option 2: ts-node/esm (
|
|
68
|
+
│ Option 2: ts-node/esm (Not Recommended - Has Module Resolution Issues) │
|
|
69
69
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
70
70
|
|
|
71
|
+
⚠️ ts-node/esm has significant limitations and is not recommended:
|
|
72
|
+
- Doesn't work with "type": "module" in package.json
|
|
73
|
+
- Module resolution doesn't work like standard TypeScript ESM
|
|
74
|
+
- Import statements must use explicit file paths
|
|
75
|
+
|
|
76
|
+
We strongly recommend using tsx/cjs instead.
|
|
77
|
+
|
|
78
|
+
If you still want to use ts-node/esm:
|
|
79
|
+
|
|
71
80
|
Installation:
|
|
72
81
|
npm install --save-dev ts-node
|
|
73
82
|
|
|
@@ -84,11 +93,12 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
|
|
|
84
93
|
"esModuleInterop": true
|
|
85
94
|
},
|
|
86
95
|
"ts-node": {
|
|
87
|
-
"esm": true
|
|
88
|
-
"experimentalSpecifierResolution": "node"
|
|
96
|
+
"esm": true
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
3. Do NOT use "type": "module" in package.json
|
|
101
|
+
|
|
92
102
|
📚 Documentation: https://codecept.io/typescript
|
|
93
103
|
|
|
94
104
|
Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
|
|
@@ -96,6 +106,34 @@ Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
|
|
|
96
106
|
`
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Get warning message if ts-node/esm is being used
|
|
111
|
+
* @param {string[]} requiredModules - Array of required modules from config
|
|
112
|
+
* @returns {string|null} Warning message or null
|
|
113
|
+
*/
|
|
114
|
+
export function getTSNodeESMWarning(requiredModules = []) {
|
|
115
|
+
if (!requiredModules.includes('ts-node/esm')) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `
|
|
120
|
+
⚠️ Warning: ts-node/esm with "module": "esnext" requires explicit file extensions in all imports.
|
|
121
|
+
|
|
122
|
+
This is a known limitation. Use tsx/cjs instead to write imports without extensions.
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
|
|
126
|
+
❌ Incorrect (will fail):
|
|
127
|
+
import loginPage from "./pages/Login";
|
|
128
|
+
|
|
129
|
+
✅ Correct (must include .ts extension):
|
|
130
|
+
import loginPage from "./pages/Login.ts";
|
|
131
|
+
|
|
132
|
+
📚 Documentation: https://codecept.io/typescript
|
|
133
|
+
|
|
134
|
+
`
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
/**
|
|
100
138
|
* Check if user is trying to run TypeScript tests without proper loader
|
|
101
139
|
* @param {string[]} testFiles - Array of test file paths
|