codeceptjs 4.0.2-beta.9 → 4.0.2
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 +15 -2
- 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 +6 -7
- 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 -1
- package/lib/command/run-workers.js +2 -14
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +84 -41
- package/lib/config.js +96 -18
- package/lib/container.js +115 -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 +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 +358 -467
- package/lib/helper/Puppeteer.js +335 -192
- 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 +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 +158 -16
- package/lib/mocha/cli.js +19 -1
- package/lib/mocha/factory.js +11 -1
- 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 +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 +10 -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 +5 -5
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- package/lib/utils/loaderCheck.js +28 -0
- 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 +188 -23
- package/lib/utils.js +77 -3
- package/lib/workers.js +65 -40
- package/package.json +35 -30
- package/typings/index.d.ts +119 -8
- package/typings/promiseBasedTypes.d.ts +3158 -6065
- package/typings/types.d.ts +3453 -6494
- 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
package/lib/plugin/pageInfo.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import Container from '../container.js'
|
|
4
|
-
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
5
4
|
import recorder from '../recorder.js'
|
|
6
5
|
import event from '../event.js'
|
|
7
6
|
import { scanForErrorMessages } from '../html.js'
|
|
7
|
+
import { captureSnapshot, pickActingHelper } from '../utils/trace.js'
|
|
8
8
|
import { output } from '../index.js'
|
|
9
|
+
import store from '../store.js'
|
|
9
10
|
import { humanizeString, ucfirst } from '../utils.js'
|
|
10
11
|
import { testToFileName } from '../mocha/test.js'
|
|
12
|
+
|
|
11
13
|
const defaultConfig = {
|
|
12
14
|
errorClasses: ['error', 'warning', 'alert', 'danger'],
|
|
13
15
|
browserLogs: ['error'],
|
|
@@ -36,67 +38,66 @@ const defaultConfig = {
|
|
|
36
38
|
*
|
|
37
39
|
*/
|
|
38
40
|
export default function (config = {}) {
|
|
39
|
-
const helpers = Container.helpers()
|
|
40
|
-
let helper
|
|
41
|
-
|
|
42
41
|
config = Object.assign(defaultConfig, config)
|
|
43
42
|
|
|
44
|
-
for (const helperName of supportedHelpers) {
|
|
45
|
-
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
46
|
-
helper = helpers[helperName]
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!helper) return // no helpers for screenshot
|
|
51
|
-
|
|
52
43
|
event.dispatcher.on(event.test.failed, test => {
|
|
44
|
+
const helper = pickActingHelper(Container.helpers())
|
|
45
|
+
if (!helper) return
|
|
46
|
+
|
|
53
47
|
const pageState = {}
|
|
54
48
|
|
|
55
|
-
recorder.add('
|
|
49
|
+
recorder.add('pageInfo capture', async () => {
|
|
56
50
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
const prefix = `${testToFileName(test)}.pageInfo`
|
|
52
|
+
const captured = await captureSnapshot(helper, {
|
|
53
|
+
dir: store.outputDir,
|
|
54
|
+
prefix,
|
|
55
|
+
captureScreenshot: false,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (captured.url) pageState.url = captured.url
|
|
59
|
+
|
|
60
|
+
if (captured.html) {
|
|
61
|
+
const htmlPath = path.join(store.outputDir, captured.html)
|
|
62
|
+
pageState.htmlSnapshot = htmlPath
|
|
63
|
+
const htmlForScan = captured.htmlRaw || (() => {
|
|
64
|
+
try { return fs.readFileSync(htmlPath, 'utf8') } catch { return '' }
|
|
65
|
+
})()
|
|
66
|
+
if (htmlForScan) {
|
|
67
|
+
try {
|
|
68
|
+
const errors = scanForErrorMessages(htmlForScan, config.errorClasses)
|
|
69
|
+
if (errors.length) {
|
|
70
|
+
output.debug('Detected errors in HTML code')
|
|
71
|
+
errors.forEach(error => output.debug(error))
|
|
72
|
+
pageState.htmlErrors = errors
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
74
76
|
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
// not really needed
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (!logs) return
|
|
78
|
+
if (captured.aria) {
|
|
79
|
+
pageState.ariaSnapshot = path.join(store.outputDir, captured.aria)
|
|
80
|
+
}
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
if (captured.console) {
|
|
83
|
+
const consolePath = path.join(store.outputDir, captured.console)
|
|
84
|
+
pageState.consoleSnapshot = consolePath
|
|
85
|
+
try {
|
|
86
|
+
const logs = JSON.parse(fs.readFileSync(consolePath, 'utf8'))
|
|
87
|
+
pageState.browserErrors = getBrowserErrors(logs, config.browserLogs)
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
}, true)
|
|
91
92
|
|
|
92
93
|
recorder.add('Save page info', () => {
|
|
93
94
|
test.addNote('pageInfo', pageStateToMarkdown(pageState))
|
|
94
95
|
|
|
95
|
-
const pageStateFileName = path.join(
|
|
96
|
+
const pageStateFileName = path.join(store.outputDir, `${testToFileName(test)}.pageInfo.md`)
|
|
96
97
|
fs.writeFileSync(pageStateFileName, pageStateToMarkdown(pageState))
|
|
97
98
|
test.artifacts.pageInfo = pageStateFileName
|
|
98
99
|
return pageState
|
|
99
|
-
})
|
|
100
|
+
}, true)
|
|
100
101
|
})
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -126,15 +127,16 @@ function pageStateToMarkdown(pageState) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function getBrowserErrors(logs, type = ['error']) {
|
|
129
|
-
// Playwright
|
|
130
|
-
|
|
130
|
+
// Accepts Playwright ConsoleMessage objects, normalized {type, text}, or strings
|
|
131
|
+
return logs
|
|
131
132
|
.map(log => {
|
|
132
133
|
if (typeof log === 'string') return log
|
|
133
|
-
if (!log
|
|
134
|
-
|
|
134
|
+
if (!log) return null
|
|
135
|
+
const t = typeof log.type === 'function' ? log.type() : log.type
|
|
136
|
+
const text = typeof log.text === 'function' ? log.text() : log.text
|
|
137
|
+
if (!t) return null
|
|
138
|
+
return { type: t, text }
|
|
135
139
|
})
|
|
136
140
|
.filter(l => l && (typeof l === 'string' || type.includes(l.type)))
|
|
137
141
|
.map(l => (typeof l === 'string' ? l : l.text))
|
|
138
|
-
|
|
139
|
-
return errors
|
|
140
142
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
import pause from '../pause.js'
|
|
3
|
+
import recorder from '../recorder.js'
|
|
4
|
+
import output from '../output.js'
|
|
5
|
+
import {
|
|
6
|
+
parsePluginArgs,
|
|
7
|
+
resolveTrigger,
|
|
8
|
+
matchStepFile,
|
|
9
|
+
matchUrl,
|
|
10
|
+
getBrowserHelper,
|
|
11
|
+
} from '../utils/pluginParser.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pauses test execution interactively. Replaces the legacy `pauseOnFail`
|
|
15
|
+
* plugin. The default `on=fail` matches the old `pauseOnFail` behavior.
|
|
16
|
+
*
|
|
17
|
+
* #### Configuration
|
|
18
|
+
*
|
|
19
|
+
* ```js
|
|
20
|
+
* plugins: {
|
|
21
|
+
* pause: {
|
|
22
|
+
* enabled: false,
|
|
23
|
+
* on: 'fail',
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* #### `on=` modes
|
|
29
|
+
*
|
|
30
|
+
* * **fail** — pause when a step fails (default)
|
|
31
|
+
* * **test** — pause after each test
|
|
32
|
+
* * **step** — pause before the first step (interactive walk-through)
|
|
33
|
+
* * **file** — pause when execution reaches `path=...[;line=...]`
|
|
34
|
+
* * **url** — pause when the browser URL matches `pattern=...`
|
|
35
|
+
*
|
|
36
|
+
* CLI examples:
|
|
37
|
+
*
|
|
38
|
+
* ```
|
|
39
|
+
* npx codeceptjs run -p pause
|
|
40
|
+
* npx codeceptjs run -p pause:on=step
|
|
41
|
+
* npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43
|
|
42
|
+
* npx codeceptjs run -p pause:on=url:pattern=/users/*
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export default function (config = {}) {
|
|
46
|
+
const cliArgs = parsePluginArgs(config._args)
|
|
47
|
+
const trigger = resolveTrigger(cliArgs, config, { on: 'fail' }, { name: 'pause' })
|
|
48
|
+
if (!trigger) return
|
|
49
|
+
|
|
50
|
+
switch (trigger.on) {
|
|
51
|
+
case 'fail':
|
|
52
|
+
return initFailMode()
|
|
53
|
+
case 'test':
|
|
54
|
+
return initTestMode()
|
|
55
|
+
case 'step':
|
|
56
|
+
return initStepMode()
|
|
57
|
+
case 'file':
|
|
58
|
+
return initFileMode(trigger.path, trigger.line)
|
|
59
|
+
case 'url':
|
|
60
|
+
return initUrlMode(trigger.pattern)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function initFailMode() {
|
|
65
|
+
let failed = false
|
|
66
|
+
|
|
67
|
+
event.dispatcher.on(event.test.started, () => {
|
|
68
|
+
failed = false
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
event.dispatcher.on(event.step.failed, () => {
|
|
72
|
+
failed = true
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
event.dispatcher.on(event.test.after, () => {
|
|
76
|
+
if (failed) pause()
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function initTestMode() {
|
|
81
|
+
event.dispatcher.on(event.test.after, () => pause())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function initStepMode() {
|
|
85
|
+
let activated = false
|
|
86
|
+
|
|
87
|
+
event.dispatcher.on(event.test.before, () => {
|
|
88
|
+
if (activated) return
|
|
89
|
+
activated = true
|
|
90
|
+
recorder.add('pause:step', () => pause())
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function initFileMode(targetPath, targetLine) {
|
|
95
|
+
let paused = false
|
|
96
|
+
|
|
97
|
+
event.dispatcher.on(event.step.before, step => {
|
|
98
|
+
if (paused) return
|
|
99
|
+
if (!matchStepFile(step, targetPath, targetLine)) return
|
|
100
|
+
paused = true
|
|
101
|
+
recorder.add('pause:file', () => pause())
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function initUrlMode(pattern) {
|
|
106
|
+
const helper = getBrowserHelper()
|
|
107
|
+
|
|
108
|
+
if (!helper) {
|
|
109
|
+
output.error('pause:on=url requires a browser helper (Playwright, WebDriver, Puppeteer, Appium)')
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let paused = false
|
|
114
|
+
|
|
115
|
+
event.dispatcher.on(event.step.after, () => {
|
|
116
|
+
if (paused) return
|
|
117
|
+
|
|
118
|
+
recorder.add('pause:url check', async () => {
|
|
119
|
+
if (paused) return
|
|
120
|
+
try {
|
|
121
|
+
const currentUrl = await helper.grabCurrentUrl()
|
|
122
|
+
if (matchUrl(currentUrl, pattern)) {
|
|
123
|
+
paused = true
|
|
124
|
+
return pause()
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// page may not be loaded yet
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
}
|
|
@@ -1,39 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import output from '../output.js'
|
|
2
|
+
import pause from './pause.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
let warned = false
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Useful for debugging flaky tests on local environment.
|
|
9
|
-
* Add this plugin to config file:
|
|
10
|
-
*
|
|
11
|
-
* ```js
|
|
12
|
-
* plugins: {
|
|
13
|
-
* pauseOnFail: {},
|
|
14
|
-
* }
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* Unlike other plugins, `pauseOnFail` is not recommended to be enabled by default.
|
|
18
|
-
* Enable it manually on each run via `-p` option:
|
|
19
|
-
*
|
|
20
|
-
* ```
|
|
21
|
-
* npx codeceptjs run -p pauseOnFail
|
|
22
|
-
* ```
|
|
7
|
+
* Starts an interactive pause when a test fails.
|
|
23
8
|
*
|
|
9
|
+
* **Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.
|
|
24
10
|
*/
|
|
25
|
-
export default function() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
event.dispatcher.on(event.step.failed, () => {
|
|
33
|
-
failed = true
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
event.dispatcher.on(event.test.after, () => {
|
|
37
|
-
if (failed) pause()
|
|
38
|
-
})
|
|
11
|
+
export default function (config = {}) {
|
|
12
|
+
if (!warned) {
|
|
13
|
+
output.error('pauseOnFail is deprecated; use the `pause` plugin (default on=fail).')
|
|
14
|
+
warned = true
|
|
15
|
+
}
|
|
16
|
+
return pause({ ...config, on: 'fail' })
|
|
39
17
|
}
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
import debugModule from 'debug'
|
|
1
2
|
import event from '../event.js'
|
|
2
|
-
|
|
3
3
|
import recorder from '../recorder.js'
|
|
4
|
-
|
|
5
4
|
import store from '../store.js'
|
|
6
5
|
|
|
6
|
+
const debug = debugModule('codeceptjs:retryFailedStep')
|
|
7
|
+
|
|
7
8
|
const defaultConfig = {
|
|
8
9
|
retries: 3,
|
|
9
10
|
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
|
|
11
|
+
minTimeout: 150,
|
|
12
|
+
maxTimeout: 10000,
|
|
10
13
|
factor: 1.5,
|
|
14
|
+
randomize: false,
|
|
11
15
|
ignoredSteps: [],
|
|
16
|
+
deferToScenarioRetries: true,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const RETRY_PRIORITIES = {
|
|
20
|
+
MANUAL_STEP: 100,
|
|
21
|
+
STEP_PLUGIN: 50,
|
|
22
|
+
SCENARIO_CONFIG: 30,
|
|
23
|
+
FEATURE_CONFIG: 20,
|
|
24
|
+
HOOK_CONFIG: 10,
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
/**
|
|
@@ -34,10 +47,9 @@ const defaultConfig = {
|
|
|
34
47
|
* #### Configuration:
|
|
35
48
|
*
|
|
36
49
|
* * `retries` - number of retries (by default 3),
|
|
37
|
-
* * `when` - function, when to perform a retry (accepts error as parameter)
|
|
38
50
|
* * `factor` - The exponential factor to use. Default is 1.5.
|
|
39
|
-
* * `minTimeout` - The number of milliseconds before starting the first retry. Default is
|
|
40
|
-
* * `maxTimeout` - The maximum number of milliseconds between two retries. Default is
|
|
51
|
+
* * `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.
|
|
52
|
+
* * `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.
|
|
41
53
|
* * `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.
|
|
42
54
|
* * `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:
|
|
43
55
|
* * `amOnPage`
|
|
@@ -49,6 +61,7 @@ const defaultConfig = {
|
|
|
49
61
|
* * `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.
|
|
50
62
|
* You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.
|
|
51
63
|
* To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.
|
|
64
|
+
* * `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.
|
|
52
65
|
*
|
|
53
66
|
* #### Example
|
|
54
67
|
*
|
|
@@ -66,7 +79,7 @@ const defaultConfig = {
|
|
|
66
79
|
*
|
|
67
80
|
* #### Disable Per Test
|
|
68
81
|
*
|
|
69
|
-
* This plugin can be disabled per test. In this case you will need to
|
|
82
|
+
* This plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:
|
|
70
83
|
*
|
|
71
84
|
* Use scenario configuration to disable plugin for a test
|
|
72
85
|
*
|
|
@@ -78,9 +91,8 @@ const defaultConfig = {
|
|
|
78
91
|
*
|
|
79
92
|
*/
|
|
80
93
|
export default function (config) {
|
|
81
|
-
config = Object.assign(defaultConfig, config)
|
|
94
|
+
config = Object.assign({}, defaultConfig, config)
|
|
82
95
|
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
83
|
-
const customWhen = config.when
|
|
84
96
|
|
|
85
97
|
let enableRetry = false
|
|
86
98
|
|
|
@@ -88,73 +100,72 @@ export default function (config) {
|
|
|
88
100
|
if (!enableRetry) return
|
|
89
101
|
if (store.debugMode) return false
|
|
90
102
|
if (!store.autoRetries) return false
|
|
91
|
-
// Don't retry terminal errors (e.g., frame detachment errors)
|
|
92
103
|
if (err && err.isTerminal) return false
|
|
93
|
-
// Don't retry navigation errors that are known to be terminal
|
|
94
104
|
if (err && err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))) return false
|
|
95
|
-
if (customWhen) return customWhen(err)
|
|
96
105
|
return true
|
|
97
106
|
}
|
|
98
107
|
config.when = when
|
|
99
108
|
|
|
100
|
-
// Ensure retry options are available before any steps run
|
|
101
109
|
if (!recorder.retries.find(r => r === config)) {
|
|
102
110
|
recorder.retries.push(config)
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
event.dispatcher.on(event.step.started, step => {
|
|
106
|
-
|
|
114
|
+
if (!step.title) return
|
|
107
115
|
for (const ignored of config.ignoredSteps) {
|
|
108
|
-
if (step.
|
|
116
|
+
if (step.title === ignored) return
|
|
109
117
|
if (ignored instanceof RegExp) {
|
|
110
|
-
if (step.
|
|
111
|
-
} else if (ignored.indexOf('*') && step.
|
|
118
|
+
if (step.title.match(ignored)) return
|
|
119
|
+
} else if (ignored.indexOf('*') !== -1 && step.title.startsWith(ignored.slice(0, -1))) return
|
|
112
120
|
}
|
|
113
|
-
enableRetry = true
|
|
121
|
+
enableRetry = true
|
|
114
122
|
})
|
|
115
123
|
|
|
116
|
-
// Disable retry only after a successful step; keep it enabled for failure so retry logic can act
|
|
117
124
|
event.dispatcher.on(event.step.passed, () => {
|
|
118
125
|
enableRetry = false
|
|
119
126
|
})
|
|
120
127
|
|
|
121
128
|
event.dispatcher.on(event.test.before, test => {
|
|
122
|
-
// pass disableRetryFailedStep is a preferred way to disable retries
|
|
123
|
-
// test.disableRetryFailedStep is used for backward compatibility
|
|
124
129
|
if (!test.opts) test.opts = {}
|
|
125
130
|
if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
|
|
126
131
|
store.autoRetries = false
|
|
127
|
-
return
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const scenarioRetries = typeof test.retries === 'function' ? test.retries() : -1
|
|
136
|
+
const stepRetryPriority = RETRY_PRIORITIES.STEP_PLUGIN
|
|
137
|
+
const scenarioPriority = test.opts.retryPriority || 0
|
|
138
|
+
|
|
139
|
+
if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
|
|
140
|
+
store.autoRetries = false
|
|
141
|
+
return
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
// Don't apply plugin retry logic if there are already manual retries configured
|
|
131
|
-
// Check if any retry configs exist that aren't from this plugin
|
|
132
144
|
const hasManualRetries = recorder.retries.some(retry => retry !== config)
|
|
133
145
|
if (hasManualRetries) {
|
|
134
146
|
store.autoRetries = false
|
|
135
147
|
return
|
|
136
148
|
}
|
|
137
149
|
|
|
138
|
-
// this option is used to set the retries inside _before() block of helpers
|
|
139
150
|
store.autoRetries = true
|
|
140
151
|
test.opts.conditionalRetries = config.retries
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
|
|
145
|
-
}
|
|
152
|
+
test.opts.stepRetryPriority = stepRetryPriority
|
|
153
|
+
|
|
154
|
+
debug('applying retries = %d for test %s', config.retries, test.title)
|
|
146
155
|
recorder.retry(config)
|
|
147
156
|
})
|
|
148
157
|
|
|
149
|
-
// Fallback for environments where event.test.before wasn't emitted (runner scenarios)
|
|
150
158
|
event.dispatcher.on(event.test.started, test => {
|
|
151
159
|
if (test.opts?.disableRetryFailedStep || test.disableRetryFailedStep) return
|
|
152
160
|
|
|
153
|
-
// Don't apply plugin retry logic if there are already manual retries configured
|
|
154
|
-
// Check if any retry configs exist that aren't from this plugin
|
|
155
161
|
const hasManualRetries = recorder.retries.some(retry => retry !== config)
|
|
156
162
|
if (hasManualRetries) return
|
|
157
163
|
|
|
164
|
+
const scenarioRetries = typeof test.retries === 'function' ? test.retries() : -1
|
|
165
|
+
if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
158
169
|
if (!store.autoRetries) {
|
|
159
170
|
store.autoRetries = true
|
|
160
171
|
test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries
|