codeceptjs 4.0.0-rc.2 → 4.0.0-rc.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 +39 -27
- package/bin/codecept.js +15 -2
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +1187 -0
- package/docs/advanced.md +201 -0
- package/docs/agents.md +159 -0
- package/docs/ai.md +537 -0
- package/docs/aitrace.md +266 -0
- package/docs/api.md +332 -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 +230 -0
- package/docs/continuous-integration.md +497 -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 +136 -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/examples.md +161 -0
- package/docs/heal.md +213 -0
- package/docs/helpers/ApiDataFactory.md +267 -0
- package/docs/helpers/Appium.md +1405 -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/Mochawesome.md +8 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/MockServer.md +212 -0
- package/docs/helpers/Playwright.md +2969 -0
- package/docs/helpers/Polly.md +44 -0
- package/docs/helpers/Protractor.md +1769 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2690 -0
- package/docs/helpers/REST.md +289 -0
- package/docs/helpers/SoftExpectHelper.md +352 -0
- package/docs/helpers/WebDriver.md +2682 -0
- package/docs/hooks.md +339 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +83 -0
- package/docs/internal-api.md +265 -0
- package/docs/internal-test-server.md +89 -0
- package/docs/locators.md +355 -0
- package/docs/mcp.md +485 -0
- package/docs/migration-4.md +556 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +399 -0
- package/docs/parallel.md +585 -0
- package/docs/playwright.md +714 -0
- package/docs/plugins.md +866 -0
- package/docs/puppeteer.md +314 -0
- package/docs/quickstart.md +120 -0
- package/docs/react.md +70 -0
- package/docs/reports.md +483 -0
- package/docs/retry.md +274 -0
- package/docs/secrets.md +150 -0
- package/docs/sessions.md +80 -0
- package/docs/shadow.md +68 -0
- package/docs/test-structure.md +275 -0
- package/docs/timeouts.md +183 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +271 -0
- package/docs/typescript.md +374 -0
- package/docs/web-element.md +251 -0
- package/docs/webdriver.md +708 -0
- package/docs/within.md +55 -0
- package/lib/ai.js +3 -2
- package/lib/aria.js +260 -0
- package/lib/assertions.js +18 -0
- package/lib/codecept.js +26 -23
- package/lib/command/check.js +2 -1
- package/lib/command/dryRun.js +24 -5
- package/lib/command/generate.js +2 -0
- package/lib/command/gherkin/snippets.js +5 -4
- package/lib/command/init.js +248 -269
- package/lib/command/list.js +150 -10
- package/lib/command/query.js +218 -0
- package/lib/command/run-multiple.js +2 -0
- package/lib/command/run-workers.js +2 -0
- package/lib/command/run.js +1 -1
- package/lib/command/workers/runTests.js +10 -10
- package/lib/config.js +77 -4
- package/lib/container.js +114 -17
- 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 +4 -3
- 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 +228 -162
- package/lib/helper/Puppeteer.js +208 -76
- package/lib/helper/WebDriver.js +173 -68
- package/lib/helper/errors/MultipleElementsFound.js +27 -110
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/Download.js +45 -0
- package/lib/helper/extras/PlaywrightReactVueLocator.js +45 -36
- 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 +4 -1
- 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 +126 -3
- package/lib/mocha/cli.js +14 -2
- package/lib/mocha/factory.js +7 -2
- package/lib/mocha/inject.js +1 -1
- package/lib/mocha/scenarioConfig.js +2 -1
- package/lib/mocha/ui.js +5 -6
- package/lib/parser.js +2 -2
- package/lib/pause.js +38 -4
- package/lib/plugin/aiTrace.js +453 -0
- package/lib/plugin/analyze.js +1 -1
- package/lib/plugin/auth.js +3 -3
- package/lib/plugin/browser.js +77 -0
- package/lib/plugin/expose.js +159 -0
- package/lib/plugin/heal.js +44 -1
- package/lib/plugin/pageInfo.js +53 -49
- package/lib/plugin/pause.js +131 -0
- package/lib/plugin/pauseOnFail.js +10 -34
- package/lib/plugin/retryFailedStep.js +28 -19
- package/lib/plugin/screencast.js +287 -0
- package/lib/plugin/screenshot.js +563 -0
- package/lib/plugin/screenshotOnFail.js +8 -171
- package/lib/rerun.js +2 -1
- package/lib/result.js +2 -1
- package/lib/step/base.js +3 -2
- package/lib/step/config.js +15 -2
- package/lib/step/record.js +2 -2
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- 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.js +77 -3
- package/lib/workers.js +52 -22
- package/package.json +19 -13
- package/typings/index.d.ts +19 -5
- 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/dontSeeCurrentPathEquals.mustache +0 -10
- 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/seeCurrentPathEquals.mustache +0 -10
- 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/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
- package/typings/promiseBasedTypes.d.ts +0 -9469
- package/typings/types.d.ts +0 -11402
|
@@ -1,178 +1,15 @@
|
|
|
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
|
-
*
|
|
48
|
-
*
|
|
7
|
+
* @deprecated Use the `screenshot` plugin with `on: 'fail'` (the default).
|
|
49
8
|
*/
|
|
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
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (options.disableScreenshots) {
|
|
75
|
-
// old version of disabling screenshots
|
|
76
|
-
return
|
|
9
|
+
export default function (config = {}) {
|
|
10
|
+
if (!warned) {
|
|
11
|
+
output.error('screenshotOnFail is deprecated; use the `screenshot` plugin (default on=fail).')
|
|
12
|
+
warned = true
|
|
77
13
|
}
|
|
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
|
-
})
|
|
14
|
+
return screenshot({ ...config, on: 'fail' })
|
|
178
15
|
}
|
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,6 +3,7 @@ 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
|
|
|
@@ -148,11 +149,11 @@ class Step {
|
|
|
148
149
|
const lines = this.stack.split('\n')
|
|
149
150
|
if (lines[STACK_LINE]) {
|
|
150
151
|
let line = lines[STACK_LINE].trim()
|
|
151
|
-
.replace(
|
|
152
|
+
.replace(store.codeceptDir || '', '.')
|
|
152
153
|
.trim()
|
|
153
154
|
|
|
154
155
|
// Map .temp.mjs back to original .ts files using container's tsFileMapping
|
|
155
|
-
const fileMapping =
|
|
156
|
+
const fileMapping = store.tsFileMapping
|
|
156
157
|
if (line.includes('.temp.mjs') && fileMapping) {
|
|
157
158
|
for (const [tsFile, mjsFile] of fileMapping.entries()) {
|
|
158
159
|
if (line.includes(mjsFile)) {
|
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/record.js
CHANGED
|
@@ -11,7 +11,7 @@ function recordStep(step, args) {
|
|
|
11
11
|
|
|
12
12
|
// apply step configuration
|
|
13
13
|
const lastArg = args[args.length - 1]
|
|
14
|
-
if (lastArg
|
|
14
|
+
if (StepConfig.isStepConfig(lastArg)) {
|
|
15
15
|
const stepConfig = args.pop()
|
|
16
16
|
const { opts, timeout, retry } = stepConfig.getConfig()
|
|
17
17
|
|
|
@@ -63,7 +63,7 @@ function recordStep(step, args) {
|
|
|
63
63
|
step.endTime = +Date.now()
|
|
64
64
|
|
|
65
65
|
// Fix error stack to point to original .ts files (lazy import to avoid circular dependency)
|
|
66
|
-
const fileMapping =
|
|
66
|
+
const fileMapping = store.tsFileMapping
|
|
67
67
|
if (fileMapping) {
|
|
68
68
|
fixErrorStack(err, fileMapping)
|
|
69
69
|
}
|
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/mask_data.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { maskSensitiveData } from 'invisi-data'
|
|
2
|
+
import store from '../store.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Mask sensitive data utility for CodeceptJS
|
|
@@ -33,7 +34,7 @@ export function maskData(input, config) {
|
|
|
33
34
|
* @returns {boolean|object} - Current masking configuration
|
|
34
35
|
*/
|
|
35
36
|
export function getMaskConfig() {
|
|
36
|
-
return global.maskSensitiveData || false
|
|
37
|
+
return store.maskSensitiveData || global.maskSensitiveData || false
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import Container from '../container.js'
|
|
2
|
+
import output from '../output.js'
|
|
3
|
+
|
|
4
|
+
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
5
|
+
|
|
6
|
+
const RESERVED_KEYS = new Set(['on', 'path', 'line', 'pattern'])
|
|
7
|
+
const ALL_MODES = ['fail', 'test', 'step', 'file', 'url']
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse a plugin's _args (from CLI `-p plugin:key=value:key=value`) into a flat dict.
|
|
11
|
+
* Each entry is split on `;` then on the first `=`. Bare segments become `{ key: true }`.
|
|
12
|
+
*
|
|
13
|
+
* Examples:
|
|
14
|
+
* parsePluginArgs(['on=fail'])
|
|
15
|
+
* → { on: 'fail' }
|
|
16
|
+
* parsePluginArgs(['on=file', 'path=tests/foo.js;line=43'])
|
|
17
|
+
* → { on: 'file', path: 'tests/foo.js', line: '43' }
|
|
18
|
+
* parsePluginArgs(['on=file', 'path=tests/foo.js', 'line=43'])
|
|
19
|
+
* → { on: 'file', path: 'tests/foo.js', line: '43' }
|
|
20
|
+
* parsePluginArgs(['show'])
|
|
21
|
+
* → { show: true }
|
|
22
|
+
*/
|
|
23
|
+
export function parsePluginArgs(args = []) {
|
|
24
|
+
const opts = {}
|
|
25
|
+
for (const arg of args) {
|
|
26
|
+
if (!arg) continue
|
|
27
|
+
for (const segment of arg.split(';')) {
|
|
28
|
+
if (!segment) continue
|
|
29
|
+
if (segment.includes('=')) {
|
|
30
|
+
const eq = segment.indexOf('=')
|
|
31
|
+
const key = segment.slice(0, eq)
|
|
32
|
+
const value = segment.slice(eq + 1)
|
|
33
|
+
opts[key] = coerce(value)
|
|
34
|
+
} else {
|
|
35
|
+
opts[segment] = true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return opts
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function coerce(v) {
|
|
43
|
+
if (v === 'true') return true
|
|
44
|
+
if (v === 'false') return false
|
|
45
|
+
return v
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compose CLI args > config > defaults into a normalized trigger spec, then
|
|
50
|
+
* validate it. Returns `{ on, path, line, pattern, ...rest }` with `line`
|
|
51
|
+
* coerced to a number, or `null` if validation failed (an error is printed).
|
|
52
|
+
*
|
|
53
|
+
* @param {object} cliArgs — output of parsePluginArgs(config._args)
|
|
54
|
+
* @param {object} config — full plugin config object
|
|
55
|
+
* @param {object} defaults — fallback values, e.g. `{ on: 'fail' }`
|
|
56
|
+
* @param {object} options
|
|
57
|
+
* @param {string} options.name — plugin name, used in error messages
|
|
58
|
+
* @param {string[]} [options.validModes] — accepted values for `on`
|
|
59
|
+
* (default: fail, test, step, file, url)
|
|
60
|
+
*/
|
|
61
|
+
export function resolveTrigger(cliArgs = {}, config = {}, defaults = {}, options = {}) {
|
|
62
|
+
const { name = 'plugin', validModes = ALL_MODES } = options
|
|
63
|
+
const merged = { ...defaults, ...pickKnown(config), ...cliArgs }
|
|
64
|
+
if (merged.line != null) merged.line = parseInt(merged.line, 10)
|
|
65
|
+
|
|
66
|
+
const valid = new Set(validModes)
|
|
67
|
+
if (!valid.has(merged.on)) {
|
|
68
|
+
output.error(`${name}: unknown on="${merged.on}". Valid: ${validModes.join(', ')}`)
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
if (merged.on === 'file' && !merged.path) {
|
|
72
|
+
output.error(`${name}:on=file requires path=. Example: -p ${name}:on=file:path=tests/foo.js`)
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
if (merged.on === 'url' && !merged.pattern) {
|
|
76
|
+
output.error(`${name}:on=url requires pattern=. Example: -p ${name}:on=url:pattern=/users/*`)
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return merged
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pickKnown(config) {
|
|
84
|
+
const out = {}
|
|
85
|
+
for (const key of Object.keys(config || {})) {
|
|
86
|
+
if (RESERVED_KEYS.has(key)) out[key] = config[key]
|
|
87
|
+
}
|
|
88
|
+
return out
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Match a step's source location against a `path` (substring/suffix) and optional `line`.
|
|
93
|
+
* Reads the step's stack via `step.line()` to get `file:row:col`.
|
|
94
|
+
*/
|
|
95
|
+
export function matchStepFile(step, targetPath, targetLine) {
|
|
96
|
+
if (!targetPath) return false
|
|
97
|
+
const stepLine = step.line && step.line()
|
|
98
|
+
if (!stepLine) return false
|
|
99
|
+
|
|
100
|
+
const parsed = parseStepLine(stepLine)
|
|
101
|
+
if (!parsed) return false
|
|
102
|
+
|
|
103
|
+
const fileMatches = parsed.file.includes(targetPath) || parsed.file.endsWith(targetPath)
|
|
104
|
+
if (!fileMatches) return false
|
|
105
|
+
|
|
106
|
+
if (targetLine != null && !Number.isNaN(targetLine) && parsed.line !== targetLine) return false
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseStepLine(stepLine) {
|
|
111
|
+
let line = stepLine.trim()
|
|
112
|
+
if (line.startsWith('at ')) line = line.substring(3).trim()
|
|
113
|
+
|
|
114
|
+
const lastColon = line.lastIndexOf(':')
|
|
115
|
+
if (lastColon < 0) return null
|
|
116
|
+
const secondLastColon = line.lastIndexOf(':', lastColon - 1)
|
|
117
|
+
if (secondLastColon < 0) return null
|
|
118
|
+
|
|
119
|
+
const file = line.substring(0, secondLastColon)
|
|
120
|
+
const lineNum = parseInt(line.substring(secondLastColon + 1, lastColon), 10)
|
|
121
|
+
|
|
122
|
+
if (Number.isNaN(lineNum)) return null
|
|
123
|
+
return { file, line: lineNum }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Match a URL string against a glob-style pattern (supports `*` wildcards).
|
|
128
|
+
*/
|
|
129
|
+
export function matchUrl(currentUrl, pattern) {
|
|
130
|
+
if (!pattern || !currentUrl) return false
|
|
131
|
+
return patternToRegex(pattern).test(currentUrl)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function patternToRegex(pattern) {
|
|
135
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
136
|
+
const regexStr = escaped.replace(/\*/g, '.*')
|
|
137
|
+
return new RegExp(regexStr)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Return the first available standard browser helper, or null.
|
|
142
|
+
*/
|
|
143
|
+
export function getBrowserHelper() {
|
|
144
|
+
const helpers = Container.helpers()
|
|
145
|
+
for (const name of supportedHelpers) {
|
|
146
|
+
if (Object.keys(helpers).indexOf(name) > -1) {
|
|
147
|
+
return helpers[name]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null
|
|
151
|
+
}
|