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/actor.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import Step, { MetaStep } from './step.js'
|
|
2
2
|
import recordStep from './step/record.js'
|
|
3
|
-
import retryStep from './step/retry.js'
|
|
4
3
|
import { methodsOfObject } from './utils.js'
|
|
5
|
-
import { TIMEOUT_ORDER } from './timeout.js'
|
|
6
4
|
import event from './event.js'
|
|
7
|
-
import store from './store.js'
|
|
8
5
|
import output from './output.js'
|
|
9
6
|
import Container from './container.js'
|
|
10
7
|
|
|
@@ -30,38 +27,6 @@ class Actor {
|
|
|
30
27
|
output.say(msg, `${color}`)
|
|
31
28
|
})
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* set the maximum execution time for the next step
|
|
36
|
-
* @function
|
|
37
|
-
* @param {number} timeout - step timeout in seconds
|
|
38
|
-
* @return {this}
|
|
39
|
-
* @inner
|
|
40
|
-
*/
|
|
41
|
-
limitTime(timeout) {
|
|
42
|
-
if (!store.timeouts) return this
|
|
43
|
-
|
|
44
|
-
console.log('I.limitTime() is deprecated, use step.timeout() instead')
|
|
45
|
-
|
|
46
|
-
event.dispatcher.prependOnceListener(event.step.before, step => {
|
|
47
|
-
output.log(`Timeout to ${step}: ${timeout}s`)
|
|
48
|
-
step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
return this
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @function
|
|
56
|
-
* @param {*} [opts]
|
|
57
|
-
* @return {this}
|
|
58
|
-
* @inner
|
|
59
|
-
*/
|
|
60
|
-
retry(opts) {
|
|
61
|
-
console.log('I.retry() is deprecated, use step.retry() instead')
|
|
62
|
-
retryStep(opts)
|
|
63
|
-
return this
|
|
64
|
-
}
|
|
65
30
|
}
|
|
66
31
|
|
|
67
32
|
/**
|
|
@@ -94,7 +59,7 @@ export default function (obj = {}, container) {
|
|
|
94
59
|
actor[action] = actor[actionAlias] = function () {
|
|
95
60
|
const step = new Step(helper, action)
|
|
96
61
|
if (translation.loaded) {
|
|
97
|
-
step.
|
|
62
|
+
step.title = actionAlias
|
|
98
63
|
step.actor = translation.I
|
|
99
64
|
}
|
|
100
65
|
// add methods to promise chain
|
package/lib/ai.js
CHANGED
|
@@ -7,6 +7,7 @@ import { generateText } from 'ai'
|
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
8
|
import path from 'path'
|
|
9
9
|
import { fileExists } from './utils.js'
|
|
10
|
+
import store from './store.js'
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
13
|
|
|
@@ -24,8 +25,8 @@ async function loadPrompts() {
|
|
|
24
25
|
for (const name of promptNames) {
|
|
25
26
|
let promptPath
|
|
26
27
|
|
|
27
|
-
if (
|
|
28
|
-
promptPath = path.join(
|
|
28
|
+
if (store.codeceptDir) {
|
|
29
|
+
promptPath = path.join(store.codeceptDir, `prompts/${name}.js`)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
if (!promptPath || !fileExists(promptPath)) {
|
package/lib/aria.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import yaml from 'js-yaml'
|
|
2
|
+
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────
|
|
4
|
+
// Roles
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const INTERACTIVE_ROLES = new Set([
|
|
8
|
+
'button',
|
|
9
|
+
'link',
|
|
10
|
+
'textbox',
|
|
11
|
+
'searchbox',
|
|
12
|
+
'checkbox',
|
|
13
|
+
'radio',
|
|
14
|
+
'switch',
|
|
15
|
+
'combobox',
|
|
16
|
+
'listbox',
|
|
17
|
+
'listitem',
|
|
18
|
+
'menu',
|
|
19
|
+
'menuitem',
|
|
20
|
+
'menuitemcheckbox',
|
|
21
|
+
'menuitemradio',
|
|
22
|
+
'option',
|
|
23
|
+
'tab',
|
|
24
|
+
'tabpanel',
|
|
25
|
+
'slider',
|
|
26
|
+
'spinbutton',
|
|
27
|
+
'treeitem',
|
|
28
|
+
'gridcell',
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
// Long groups of same-role siblings get summarised as: first N + "...M omitted..." + last N
|
|
32
|
+
const SIBLING_COLLAPSE_THRESHOLD = 50
|
|
33
|
+
const SIBLING_COLLAPSE_KEEP_EACH_SIDE = 5
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────
|
|
36
|
+
// STEP 1 · Parse: YAML text → AriaNode[]
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function unquote(value) {
|
|
40
|
+
const v = value.trim()
|
|
41
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
42
|
+
return v.slice(1, -1)
|
|
43
|
+
}
|
|
44
|
+
return v
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Parse one YAML node label like: `button "Save"`, `textbox "Email" [focused]`, `heading "Title" [level=2]`
|
|
48
|
+
function parseLabel(label) {
|
|
49
|
+
if (!label) return null
|
|
50
|
+
const trimmed = label.trim()
|
|
51
|
+
const roleMatch = trimmed.match(/^(\w+)/)
|
|
52
|
+
if (!roleMatch) return null
|
|
53
|
+
const role = roleMatch[1].toLowerCase()
|
|
54
|
+
let rest = trimmed.slice(roleMatch[0].length)
|
|
55
|
+
|
|
56
|
+
let name
|
|
57
|
+
const nameMatch = rest.match(/^\s*"((?:[^"\\]|\\.)*)"/) || rest.match(/^\s*'((?:[^'\\]|\\.)*)'/)
|
|
58
|
+
if (nameMatch) {
|
|
59
|
+
name = nameMatch[1]
|
|
60
|
+
rest = rest.slice(nameMatch[0].length)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const attributes = {}
|
|
64
|
+
const attrMatch = rest.match(/\[([^\]]*)\]/)
|
|
65
|
+
if (attrMatch) {
|
|
66
|
+
for (const tok of attrMatch[1].split(/[\s,]+/).filter(Boolean)) {
|
|
67
|
+
const eq = tok.indexOf('=')
|
|
68
|
+
if (eq === -1) {
|
|
69
|
+
attributes[tok.toLowerCase()] = true
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
attributes[tok.slice(0, eq).trim().toLowerCase()] = unquote(tok.slice(eq + 1))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { role, name, attributes }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function yamlItemToNode(item) {
|
|
80
|
+
if (typeof item === 'string') {
|
|
81
|
+
const label = parseLabel(item)
|
|
82
|
+
if (!label) return null
|
|
83
|
+
const node = { role: label.role, name: label.name, attributes: label.attributes, children: [] }
|
|
84
|
+
return node
|
|
85
|
+
}
|
|
86
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return null
|
|
87
|
+
|
|
88
|
+
const entries = Object.entries(item)
|
|
89
|
+
if (entries.length === 0) return null
|
|
90
|
+
const [key, value] = entries[0]
|
|
91
|
+
const label = parseLabel(key)
|
|
92
|
+
if (!label) return null
|
|
93
|
+
const node = { role: label.role, name: label.name, attributes: label.attributes, children: [] }
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(value)) {
|
|
96
|
+
node.children = value.map(yamlItemToNode).filter(n => n !== null)
|
|
97
|
+
return node
|
|
98
|
+
}
|
|
99
|
+
if (value !== null && value !== undefined) node.value = String(value)
|
|
100
|
+
return node
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseSnapshot(snapshot) {
|
|
104
|
+
if (!snapshot) return []
|
|
105
|
+
let parsed
|
|
106
|
+
try {
|
|
107
|
+
parsed = yaml.load(snapshot)
|
|
108
|
+
} catch {
|
|
109
|
+
return []
|
|
110
|
+
}
|
|
111
|
+
if (!Array.isArray(parsed)) return []
|
|
112
|
+
return parsed.map(yamlItemToNode).filter(n => n !== null)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─────────────────────────────────────────────────────────────────
|
|
116
|
+
// STEP 2 · Transform: drop containers that contribute nothing.
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
function dropEmpty(nodes) {
|
|
120
|
+
return nodes.flatMap(node => {
|
|
121
|
+
const children = dropEmpty(node.children)
|
|
122
|
+
if (INTERACTIVE_ROLES.has(node.role)) return [{ ...node, children }]
|
|
123
|
+
if (children.length > 0) return [{ ...node, children }]
|
|
124
|
+
return []
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────
|
|
129
|
+
// STEP 3 · Render: AriaNode[] → indented YAML text
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
// One-line representation of a node. Stable attr order so diff comparisons are deterministic.
|
|
133
|
+
function formatNode(node) {
|
|
134
|
+
let line = node.role
|
|
135
|
+
if (node.name && node.name.trim()) line += ` "${node.name.trim()}"`
|
|
136
|
+
const attrParts = []
|
|
137
|
+
for (const k of Object.keys(node.attributes).sort()) {
|
|
138
|
+
const v = node.attributes[k]
|
|
139
|
+
if (v === undefined || v === null || v === '') continue
|
|
140
|
+
if (v === true) attrParts.push(k)
|
|
141
|
+
else attrParts.push(`${k}=${v}`)
|
|
142
|
+
}
|
|
143
|
+
if (attrParts.length > 0) line += ` [${attrParts.join(' ')}]`
|
|
144
|
+
if (node.value !== undefined && node.value !== null) {
|
|
145
|
+
const text = String(node.value).trim()
|
|
146
|
+
if (text) line += `: ${text}`
|
|
147
|
+
}
|
|
148
|
+
return line
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Group consecutive same-role siblings. [a,a,b,a,a,a] → [[a,a],[b],[a,a,a]]
|
|
152
|
+
function groupByConsecutiveRole(nodes) {
|
|
153
|
+
return nodes.reduce((groups, node) => {
|
|
154
|
+
const last = groups[groups.length - 1]
|
|
155
|
+
if (last && last[0].role === node.role) {
|
|
156
|
+
last.push(node)
|
|
157
|
+
return groups
|
|
158
|
+
}
|
|
159
|
+
groups.push([node])
|
|
160
|
+
return groups
|
|
161
|
+
}, [])
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Large group of same-role siblings → first N + placeholder line + last N.
|
|
165
|
+
// Returns mix of AriaNode (to render) and pre-rendered placeholder strings.
|
|
166
|
+
function collapseGroup(group, depth) {
|
|
167
|
+
if (group.length <= SIBLING_COLLAPSE_THRESHOLD) return group
|
|
168
|
+
const keep = SIBLING_COLLAPSE_KEEP_EACH_SIDE
|
|
169
|
+
const omitted = group.length - keep * 2
|
|
170
|
+
const placeholder = `${' '.repeat(depth)}- ...${omitted} similar "${group[0].role}" items omitted...`
|
|
171
|
+
return [...group.slice(0, keep), placeholder, ...group.slice(-keep)]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderTree(nodes, depth = 0) {
|
|
175
|
+
const items = groupByConsecutiveRole(nodes).flatMap(group => collapseGroup(group, depth))
|
|
176
|
+
return items
|
|
177
|
+
.map(item => {
|
|
178
|
+
if (typeof item === 'string') return item
|
|
179
|
+
const indent = ' '.repeat(depth)
|
|
180
|
+
const head = `${indent}- ${formatNode(item)}`
|
|
181
|
+
if (item.children.length === 0) return head
|
|
182
|
+
return `${head}:\n${renderTree(item.children, depth + 1)}`
|
|
183
|
+
})
|
|
184
|
+
.join('\n')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─────────────────────────────────────────────────────────────────
|
|
188
|
+
// STEP 4 · Diff: collect interactive summaries → bag diff → text
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
// Walk tree, emit one summary string per meaningful interactive node.
|
|
192
|
+
function collectSummaries(nodes) {
|
|
193
|
+
return nodes.flatMap(node => {
|
|
194
|
+
const fromChildren = collectSummaries(node.children)
|
|
195
|
+
if (!INTERACTIVE_ROLES.has(node.role)) return fromChildren
|
|
196
|
+
const summary = formatNode(node)
|
|
197
|
+
if (summary === node.role) return fromChildren // skip empty unnamed interactive nodes
|
|
198
|
+
return [summary, ...fromChildren]
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function countBy(items) {
|
|
203
|
+
return items.reduce((map, item) => {
|
|
204
|
+
map.set(item, (map.get(item) ?? 0) + 1)
|
|
205
|
+
return map
|
|
206
|
+
}, new Map())
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Bag diff: any summary appearing more in one bag than the other becomes added/removed.
|
|
210
|
+
function diffSummaries(prev, curr) {
|
|
211
|
+
const before = countBy(prev)
|
|
212
|
+
const after = countBy(curr)
|
|
213
|
+
const added = []
|
|
214
|
+
const removed = []
|
|
215
|
+
for (const summary of new Set([...before.keys(), ...after.keys()])) {
|
|
216
|
+
const b = before.get(summary) ?? 0
|
|
217
|
+
const a = after.get(summary) ?? 0
|
|
218
|
+
for (let i = 0; i < a - b; i += 1) added.push(summary)
|
|
219
|
+
for (let i = 0; i < b - a; i += 1) removed.push(summary)
|
|
220
|
+
}
|
|
221
|
+
return { added, removed }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function formatDiff(added, removed) {
|
|
225
|
+
if (added.length === 0 && removed.length === 0) return null
|
|
226
|
+
const lines = ['ariaDiff:']
|
|
227
|
+
if (added.length === 0) {
|
|
228
|
+
lines.push(' added: []')
|
|
229
|
+
} else {
|
|
230
|
+
lines.push(' added:')
|
|
231
|
+
for (const [item, count] of [...countBy(added).entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
232
|
+
const suffix = count > 1 ? ` (x${count})` : ''
|
|
233
|
+
lines.push(` - ${item}${suffix}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (removed.length === 0) {
|
|
237
|
+
lines.push(' removed: []')
|
|
238
|
+
} else {
|
|
239
|
+
lines.push(` removed: ${removed.length} interactive elements`)
|
|
240
|
+
}
|
|
241
|
+
return lines.join('\n')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────
|
|
245
|
+
// Public API — pipelines composed visibly, top-to-bottom
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function compactAriaSnapshot(snapshot) {
|
|
249
|
+
if (!snapshot) return ''
|
|
250
|
+
const tree = dropEmpty(parseSnapshot(snapshot))
|
|
251
|
+
return renderTree(tree)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function diffAriaSnapshots(previous, current) {
|
|
255
|
+
const summariesOf = snap => collectSummaries(dropEmpty(parseSnapshot(snap)))
|
|
256
|
+
const { added, removed } = diffSummaries(summariesOf(previous), summariesOf(current))
|
|
257
|
+
return formatDiff(added, removed)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { diffAriaSnapshots, compactAriaSnapshot }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Assertion from './assert.js'
|
|
2
|
+
import { equals, urlEquals, fileEquals } from './assert/equal.js'
|
|
3
|
+
import { includes, fileIncludes } from './assert/include.js'
|
|
4
|
+
import { empty } from './assert/empty.js'
|
|
5
|
+
import { truth } from './assert/truth.js'
|
|
6
|
+
|
|
7
|
+
export { Assertion, equals, urlEquals, fileEquals, includes, fileIncludes, empty, truth }
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
Assertion,
|
|
11
|
+
equals,
|
|
12
|
+
urlEquals,
|
|
13
|
+
fileEquals,
|
|
14
|
+
includes,
|
|
15
|
+
fileIncludes,
|
|
16
|
+
empty,
|
|
17
|
+
truth,
|
|
18
|
+
}
|
package/lib/codecept.js
CHANGED
|
@@ -19,8 +19,9 @@ import ActorFactory from './actor.js'
|
|
|
19
19
|
import output from './output.js'
|
|
20
20
|
import { emptyFolder } from './utils.js'
|
|
21
21
|
import { initCodeceptGlobals } from './globals.js'
|
|
22
|
-
import { validateTypeScriptSetup } from './utils/loaderCheck.js'
|
|
22
|
+
import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
|
|
23
23
|
import recorder from './recorder.js'
|
|
24
|
+
import store from './store.js'
|
|
24
25
|
|
|
25
26
|
import storeListener from './listener/store.js'
|
|
26
27
|
import stepsListener from './listener/steps.js'
|
|
@@ -71,7 +72,7 @@ class Codecept {
|
|
|
71
72
|
} else {
|
|
72
73
|
// For npm packages, resolve from the user's directory
|
|
73
74
|
// This ensures packages like tsx are found in user's node_modules
|
|
74
|
-
const userDir =
|
|
75
|
+
const userDir = store.codeceptDir || process.cwd()
|
|
75
76
|
|
|
76
77
|
try {
|
|
77
78
|
// Use createRequire to resolve from user's directory
|
|
@@ -102,8 +103,6 @@ class Codecept {
|
|
|
102
103
|
await this.requireModules(this.requiringModules)
|
|
103
104
|
// initializing listeners
|
|
104
105
|
await container.create(this.config, this.opts)
|
|
105
|
-
// Store container globally for easy access
|
|
106
|
-
global.container = container
|
|
107
106
|
await this.runHooks()
|
|
108
107
|
}
|
|
109
108
|
|
|
@@ -120,23 +119,27 @@ class Codecept {
|
|
|
120
119
|
* Executes hooks.
|
|
121
120
|
*/
|
|
122
121
|
async runHooks() {
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
122
|
+
// For workers parent process we only need plugins/hooks.
|
|
123
|
+
// Core listeners are executed inside worker threads.
|
|
124
|
+
if (!this.opts?.skipDefaultListeners) {
|
|
125
|
+
const listenerModules = [
|
|
126
|
+
'./listener/store.js',
|
|
127
|
+
'./listener/steps.js',
|
|
128
|
+
'./listener/config.js',
|
|
129
|
+
'./listener/result.js',
|
|
130
|
+
'./listener/helpers.js',
|
|
131
|
+
'./listener/pageobjects.js',
|
|
132
|
+
'./listener/globalTimeout.js',
|
|
133
|
+
'./listener/globalRetry.js',
|
|
134
|
+
'./listener/retryEnhancer.js',
|
|
135
|
+
'./listener/exit.js',
|
|
136
|
+
'./listener/emptyRun.js',
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
for (const modulePath of listenerModules) {
|
|
140
|
+
const module = await import(modulePath)
|
|
141
|
+
runHook(module.default || module)
|
|
142
|
+
}
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// custom hooks (previous iteration of plugins)
|
|
@@ -168,7 +171,7 @@ class Codecept {
|
|
|
168
171
|
*/
|
|
169
172
|
loadTests(pattern) {
|
|
170
173
|
const options = {
|
|
171
|
-
cwd:
|
|
174
|
+
cwd: store.codeceptDir,
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
let patterns = [pattern]
|
|
@@ -200,7 +203,7 @@ class Codecept {
|
|
|
200
203
|
globSync(pattern, options).forEach(file => {
|
|
201
204
|
if (file.includes('node_modules')) return
|
|
202
205
|
if (!fsPath.isAbsolute(file)) {
|
|
203
|
-
file = fsPath.join(
|
|
206
|
+
file = fsPath.join(store.codeceptDir, file)
|
|
204
207
|
}
|
|
205
208
|
if (!this.testFiles.includes(fsPath.resolve(file))) {
|
|
206
209
|
this.testFiles.push(fsPath.resolve(file))
|
|
@@ -270,6 +273,12 @@ class Codecept {
|
|
|
270
273
|
process.exit(1)
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
// Show warning if ts-node/esm is being used
|
|
277
|
+
const tsWarning = getTSNodeESMWarning(this.requiringModules || [])
|
|
278
|
+
if (tsWarning) {
|
|
279
|
+
output.print(output.colors.yellow(tsWarning))
|
|
280
|
+
}
|
|
281
|
+
|
|
273
282
|
// Ensure translations are loaded for Gherkin features
|
|
274
283
|
try {
|
|
275
284
|
const { loadTranslations } = await import('./mocha/gherkin.js')
|
|
@@ -284,7 +293,7 @@ class Codecept {
|
|
|
284
293
|
|
|
285
294
|
if (test) {
|
|
286
295
|
if (!fsPath.isAbsolute(test)) {
|
|
287
|
-
test = fsPath.join(
|
|
296
|
+
test = fsPath.join(store.codeceptDir, test)
|
|
288
297
|
}
|
|
289
298
|
const testBasename = fsPath.basename(test, '.js')
|
|
290
299
|
const testFeatureBasename = fsPath.basename(test, '.feature')
|
|
@@ -307,7 +316,7 @@ class Codecept {
|
|
|
307
316
|
|
|
308
317
|
try {
|
|
309
318
|
event.emit(event.all.before, this)
|
|
310
|
-
mocha.run(async (failures) => await done(failures))
|
|
319
|
+
mocha.runner = mocha.run(async (failures) => await done(failures))
|
|
311
320
|
} catch (e) {
|
|
312
321
|
output.error(e.stack)
|
|
313
322
|
reject(e)
|
package/lib/command/check.js
CHANGED
|
@@ -135,6 +135,7 @@ export default async function (options) {
|
|
|
135
135
|
printCheck('plugins', checks['plugins'], Object.keys(Container.plugins()).join(', '))
|
|
136
136
|
|
|
137
137
|
if (Object.keys(helpers).length) {
|
|
138
|
+
store.dryRun = false
|
|
138
139
|
const suite = Container.mocha().suite
|
|
139
140
|
const test = createTest('test', () => {})
|
|
140
141
|
checks.setup = true
|
|
@@ -154,7 +155,6 @@ export default async function (options) {
|
|
|
154
155
|
checks.teardown = true
|
|
155
156
|
for (const helper of Object.values(helpers).reverse()) {
|
|
156
157
|
try {
|
|
157
|
-
if (helper._passed) await helper._passed(test)
|
|
158
158
|
if (helper._after) await helper._after(test)
|
|
159
159
|
if (helper._finishTest) await helper._finishTest(suite)
|
|
160
160
|
if (helper._afterSuite) await helper._afterSuite(suite)
|
|
@@ -166,6 +166,7 @@ export default async function (options) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
printCheck('Helpers After', checks['teardown'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Closing browser' : '')
|
|
169
|
+
store.dryRun = true
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
try {
|
|
@@ -229,13 +229,12 @@ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue)
|
|
|
229
229
|
const importStrings = []
|
|
230
230
|
|
|
231
231
|
for (const name in pathsToType) {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
232
|
+
const originalPath = pathsToType[name]
|
|
233
|
+
const relativePath = getPath(originalPath, targetFolderPath, testsPath)
|
|
234
|
+
// 4.x is ESM-first: step files and page objects use `export default`,
|
|
235
|
+
// so the type is reached via `.default` regardless of file extension
|
|
236
|
+
// (.js, .ts, or no extension, as set in `include`).
|
|
237
|
+
importStrings.push(`type ${name} = typeof import('${relativePath}').default;`)
|
|
239
238
|
}
|
|
240
239
|
|
|
241
240
|
for (const name in pathsToValue) {
|
package/lib/command/dryRun.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
1
2
|
import { getConfig, getTestRoot } from './utils.js'
|
|
2
3
|
import Config from '../config.js'
|
|
3
4
|
import Codecept from '../codecept.js'
|
|
@@ -8,6 +9,8 @@ import Container from '../container.js'
|
|
|
8
9
|
|
|
9
10
|
export default async function (test, options) {
|
|
10
11
|
if (options.grep) process.env.grep = options.grep
|
|
12
|
+
if (options.ansi === false) chalk.level = 0
|
|
13
|
+
store.dryRun = true
|
|
11
14
|
const configFile = options.config
|
|
12
15
|
let codecept
|
|
13
16
|
|
|
@@ -18,10 +21,15 @@ export default async function (test, options) {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
if (config.plugins) {
|
|
21
|
-
//
|
|
24
|
+
// Disable plugins that block (interactive) or perform external I/O (AI/network).
|
|
25
|
+
// Leave the rest enabled so they can register support objects (e.g. auth registers
|
|
26
|
+
// `login`); helper calls inside those support fns are already no-op'd by HelperStep
|
|
27
|
+
// when store.dryRun is true.
|
|
28
|
+
const disableInDryRun = new Set(['pause', 'pauseOnFail', 'analyze', 'aiTrace', 'pageInfo', 'heal'])
|
|
22
29
|
for (const plugin in config.plugins) {
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
if (disableInDryRun.has(plugin)) {
|
|
31
|
+
config.plugins[plugin].enabled = false
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
|
|
@@ -32,12 +40,12 @@ export default async function (test, options) {
|
|
|
32
40
|
if (options.bootstrap) await codecept.bootstrap()
|
|
33
41
|
|
|
34
42
|
codecept.loadTests()
|
|
35
|
-
store.dryRun = true
|
|
36
43
|
|
|
37
44
|
if (!options.steps && !options.verbose && !options.debug) {
|
|
38
45
|
await printTests(codecept.testFiles)
|
|
39
46
|
return
|
|
40
47
|
}
|
|
48
|
+
if (options.numbers) numberSteps()
|
|
41
49
|
event.dispatcher.on(event.all.result, printFooter)
|
|
42
50
|
await codecept.run(test)
|
|
43
51
|
} catch (err) {
|
|
@@ -46,11 +54,22 @@ export default async function (test, options) {
|
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
|
|
57
|
+
function numberSteps() {
|
|
58
|
+
let stepIndex = 0
|
|
59
|
+
event.dispatcher.on(event.test.before, () => {
|
|
60
|
+
stepIndex = 0
|
|
61
|
+
})
|
|
62
|
+
event.dispatcher.prependListener(event.step.before, step => {
|
|
63
|
+
stepIndex++
|
|
64
|
+
step.prefix = `${stepIndex}. ${step.prefix || ''}`
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
async function printTests(files) {
|
|
50
69
|
const { default: figures } = await import('figures')
|
|
51
70
|
const { default: colors } = await import('chalk')
|
|
52
71
|
|
|
53
|
-
output.print(output.styles.debug(`Tests from ${
|
|
72
|
+
output.print(output.styles.debug(`Tests from ${store.codeceptDir}:`))
|
|
54
73
|
output.print()
|
|
55
74
|
|
|
56
75
|
const mocha = Container.mocha()
|
package/lib/command/generate.js
CHANGED
|
@@ -5,6 +5,7 @@ import { mkdirp } from 'mkdirp'
|
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import { fileExists, ucfirst, lcfirst, beautify } from '../utils.js'
|
|
7
7
|
import output from '../output.js'
|
|
8
|
+
import store from '../store.js'
|
|
8
9
|
import generateDefinitions from './definitions.js'
|
|
9
10
|
import { getConfig, getTestRoot, safeFileWrite, readConfig } from './utils.js'
|
|
10
11
|
|
|
@@ -20,6 +21,7 @@ Scenario('test something', async ({ {{actor}} }) => {
|
|
|
20
21
|
// generates empty test
|
|
21
22
|
export async function test(genPath) {
|
|
22
23
|
const testsPath = getTestRoot(genPath)
|
|
24
|
+
store.codeceptDir = testsPath
|
|
23
25
|
global.codecept_dir = testsPath
|
|
24
26
|
const config = await getConfig(testsPath)
|
|
25
27
|
if (!config) return
|
|
@@ -85,7 +87,7 @@ export default {
|
|
|
85
87
|
|
|
86
88
|
const poModuleTemplateTS = `const { I } = inject();
|
|
87
89
|
|
|
88
|
-
export
|
|
90
|
+
export default {
|
|
89
91
|
|
|
90
92
|
// insert your locators and methods here
|
|
91
93
|
}
|
|
@@ -8,6 +8,7 @@ import fsPath from 'path'
|
|
|
8
8
|
import { getConfig, getTestRoot } from '../utils.js'
|
|
9
9
|
import Codecept from '../../codecept.js'
|
|
10
10
|
import output from '../../output.js'
|
|
11
|
+
import store from '../../store.js'
|
|
11
12
|
import { matchStep } from '../../mocha/bdd.js'
|
|
12
13
|
|
|
13
14
|
const uuidFn = IdGenerator.uuid()
|
|
@@ -43,9 +44,9 @@ export default async function (genPath, options) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const files = []
|
|
46
|
-
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' :
|
|
47
|
+
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : store.codeceptDir }).forEach(file => {
|
|
47
48
|
if (!fsPath.isAbsolute(file)) {
|
|
48
|
-
file = fsPath.join(
|
|
49
|
+
file = fsPath.join(store.codeceptDir, file)
|
|
49
50
|
}
|
|
50
51
|
files.push(fsPath.resolve(file))
|
|
51
52
|
})
|
|
@@ -92,7 +93,7 @@ export default async function (genPath, options) {
|
|
|
92
93
|
if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
|
|
93
94
|
parseSteps(child.scenario.steps)
|
|
94
95
|
.map(step => {
|
|
95
|
-
return Object.assign(step, { file: file.replace(
|
|
96
|
+
return Object.assign(step, { file: file.replace(store.codeceptDir, '').slice(1) })
|
|
96
97
|
})
|
|
97
98
|
.map(step => newSteps.set(`${step.type}(${step})`, step))
|
|
98
99
|
}
|
|
@@ -107,7 +108,7 @@ export default async function (genPath, options) {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
if (!fsPath.isAbsolute(stepFile)) {
|
|
110
|
-
stepFile = fsPath.join(
|
|
111
|
+
stepFile = fsPath.join(store.codeceptDir, stepFile)
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const snippets = [...newSteps.values()]
|