codeceptjs 3.7.3 → 3.7.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,6 +38,7 @@ const { highlightElement } = require('./scripts/highlightElement')
38
38
  const { blurElement } = require('./scripts/blurElement')
39
39
  const { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } = require('./errors/ElementAssertion')
40
40
  const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
41
+ const WebElement = require('../element/WebElement')
41
42
 
42
43
  let puppeteer
43
44
  let perfTiming
@@ -633,9 +634,11 @@ class Puppeteer extends Helper {
633
634
  return
634
635
  }
635
636
 
636
- const els = await this._locate(locator)
637
- assertElementExists(els, locator)
638
- this.context = els[0]
637
+ const el = await this._locateElement(locator)
638
+ if (!el) {
639
+ throw new ElementNotFound(locator, 'Element for within context')
640
+ }
641
+ this.context = el
639
642
 
640
643
  this.withinLocator = new Locator(locator)
641
644
  }
@@ -729,11 +732,13 @@ class Puppeteer extends Helper {
729
732
  * {{ react }}
730
733
  */
731
734
  async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
732
- const els = await this._locate(locator)
733
- assertElementExists(els, locator)
735
+ const el = await this._locateElement(locator)
736
+ if (!el) {
737
+ throw new ElementNotFound(locator, 'Element to move cursor to')
738
+ }
734
739
 
735
740
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
736
- const { x, y } = await getClickablePoint(els[0])
741
+ const { x, y } = await getClickablePoint(el)
737
742
  await this.page.mouse.move(x + offsetX, y + offsetY)
738
743
  return this._waitForAction()
739
744
  }
@@ -743,9 +748,10 @@ class Puppeteer extends Helper {
743
748
  *
744
749
  */
745
750
  async focus(locator) {
746
- const els = await this._locate(locator)
747
- assertElementExists(els, locator, 'Element to focus')
748
- const el = els[0]
751
+ const el = await this._locateElement(locator)
752
+ if (!el) {
753
+ throw new ElementNotFound(locator, 'Element to focus')
754
+ }
749
755
 
750
756
  await el.click()
751
757
  await el.focus()
@@ -757,10 +763,12 @@ class Puppeteer extends Helper {
757
763
  *
758
764
  */
759
765
  async blur(locator) {
760
- const els = await this._locate(locator)
761
- assertElementExists(els, locator, 'Element to blur')
766
+ const el = await this._locateElement(locator)
767
+ if (!el) {
768
+ throw new ElementNotFound(locator, 'Element to blur')
769
+ }
762
770
 
763
- await blurElement(els[0], this.page)
771
+ await blurElement(el, this.page)
764
772
  return this._waitForAction()
765
773
  }
766
774
 
@@ -809,11 +817,12 @@ class Puppeteer extends Helper {
809
817
  }
810
818
 
811
819
  if (locator) {
812
- const els = await this._locate(locator)
813
- assertElementExists(els, locator, 'Element')
814
- const el = els[0]
820
+ const el = await this._locateElement(locator)
821
+ if (!el) {
822
+ throw new ElementNotFound(locator, 'Element to scroll into view')
823
+ }
815
824
  await el.evaluate(el => el.scrollIntoView())
816
- const elementCoordinates = await getClickablePoint(els[0])
825
+ const elementCoordinates = await getClickablePoint(el)
817
826
  await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY)
818
827
  } else {
819
828
  await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
@@ -881,6 +890,21 @@ class Puppeteer extends Helper {
881
890
  return findElements.call(this, context, locator)
882
891
  }
883
892
 
893
+ /**
894
+ * Get single element by different locator types, including strict locator
895
+ * Should be used in custom helpers:
896
+ *
897
+ * ```js
898
+ * const element = await this.helpers['Puppeteer']._locateElement({name: 'password'});
899
+ * ```
900
+ *
901
+ * {{ react }}
902
+ */
903
+ async _locateElement(locator) {
904
+ const context = await this.context
905
+ return findElement.call(this, context, locator)
906
+ }
907
+
884
908
  /**
885
909
  * Find a checkbox by providing human-readable text:
886
910
  * NOTE: Assumes the checkable element exists
@@ -892,7 +916,9 @@ class Puppeteer extends Helper {
892
916
  async _locateCheckable(locator, providedContext = null) {
893
917
  const context = providedContext || (await this._getContext())
894
918
  const els = await findCheckable.call(this, locator, context)
895
- assertElementExists(els[0], locator, 'Checkbox or radio')
919
+ if (!els || els.length === 0) {
920
+ throw new ElementNotFound(locator, 'Checkbox or radio')
921
+ }
896
922
  return els[0]
897
923
  }
898
924
 
@@ -924,7 +950,20 @@ class Puppeteer extends Helper {
924
950
  *
925
951
  */
926
952
  async grabWebElements(locator) {
927
- return this._locate(locator)
953
+ const elements = await this._locate(locator)
954
+ return elements.map(element => new WebElement(element, this))
955
+ }
956
+
957
+ /**
958
+ * {{> grabWebElement }}
959
+ *
960
+ */
961
+ async grabWebElement(locator) {
962
+ const elements = await this._locate(locator)
963
+ if (elements.length === 0) {
964
+ throw new ElementNotFound(locator, 'Element')
965
+ }
966
+ return new WebElement(elements[0], this)
928
967
  }
929
968
 
930
969
  /**
@@ -2110,10 +2149,12 @@ class Puppeteer extends Helper {
2110
2149
  * {{> waitForClickable }}
2111
2150
  */
2112
2151
  async waitForClickable(locator, waitTimeout) {
2113
- const els = await this._locate(locator)
2114
- assertElementExists(els, locator)
2152
+ const el = await this._locateElement(locator)
2153
+ if (!el) {
2154
+ throw new ElementNotFound(locator, 'Element to wait for clickable')
2155
+ }
2115
2156
 
2116
- return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async e => {
2157
+ return this.waitForFunction(isElementClickable, [el], waitTimeout).catch(async e => {
2117
2158
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2118
2159
  throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`)
2119
2160
  } else {
@@ -2687,9 +2728,18 @@ class Puppeteer extends Helper {
2687
2728
 
2688
2729
  module.exports = Puppeteer
2689
2730
 
2731
+ /**
2732
+ * Find elements using Puppeteer's native element discovery methods
2733
+ * Note: Unlike Playwright, Puppeteer's Locator API doesn't have .all() method for multiple elements
2734
+ * @param {Object} matcher - Puppeteer context to search within
2735
+ * @param {Object|string} locator - Locator specification
2736
+ * @returns {Promise<Array>} Array of ElementHandle objects
2737
+ */
2690
2738
  async function findElements(matcher, locator) {
2691
2739
  if (locator.react) return findReactElements.call(this, locator)
2692
2740
  locator = new Locator(locator, 'css')
2741
+
2742
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
2693
2743
  if (!locator.isXPath()) return matcher.$$(locator.simplify())
2694
2744
  // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2695
2745
  if (puppeteer.default?.defaultBrowserRevision) {
@@ -2698,6 +2748,31 @@ async function findElements(matcher, locator) {
2698
2748
  return matcher.$x(locator.value)
2699
2749
  }
2700
2750
 
2751
+ /**
2752
+ * Find a single element using Puppeteer's native element discovery methods
2753
+ * Note: Puppeteer Locator API doesn't have .first() method like Playwright
2754
+ * @param {Object} matcher - Puppeteer context to search within
2755
+ * @param {Object|string} locator - Locator specification
2756
+ * @returns {Promise<Object>} Single ElementHandle object
2757
+ */
2758
+ async function findElement(matcher, locator) {
2759
+ if (locator.react) return findReactElements.call(this, locator)
2760
+ locator = new Locator(locator, 'css')
2761
+
2762
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .first() method
2763
+ if (!locator.isXPath()) {
2764
+ const elements = await matcher.$$(locator.simplify())
2765
+ return elements[0]
2766
+ }
2767
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2768
+ if (puppeteer.default?.defaultBrowserRevision) {
2769
+ const elements = await matcher.$$(`xpath/${locator.value}`)
2770
+ return elements[0]
2771
+ }
2772
+ const elements = await matcher.$x(locator.value)
2773
+ return elements[0]
2774
+ }
2775
+
2701
2776
  async function proceedClick(locator, context = null, options = {}) {
2702
2777
  let matcher = await this.context
2703
2778
  if (context) {
@@ -2843,15 +2918,19 @@ async function findFields(locator) {
2843
2918
  }
2844
2919
 
2845
2920
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
2846
- const src = await this._locate(sourceLocator)
2847
- assertElementExists(src, sourceLocator, 'Source Element')
2921
+ const src = await this._locateElement(sourceLocator)
2922
+ if (!src) {
2923
+ throw new ElementNotFound(sourceLocator, 'Source Element')
2924
+ }
2848
2925
 
2849
- const dst = await this._locate(destinationLocator)
2850
- assertElementExists(dst, destinationLocator, 'Destination Element')
2926
+ const dst = await this._locateElement(destinationLocator)
2927
+ if (!dst) {
2928
+ throw new ElementNotFound(destinationLocator, 'Destination Element')
2929
+ }
2851
2930
 
2852
- // Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
2853
- const dragSource = await getClickablePoint(src[0])
2854
- const dragDestination = await getClickablePoint(dst[0])
2931
+ // Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
2932
+ const dragSource = await getClickablePoint(src)
2933
+ const dragDestination = await getClickablePoint(dst)
2855
2934
 
2856
2935
  // Drag start point
2857
2936
  await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
@@ -21,6 +21,7 @@ const { focusElement } = require('./scripts/focusElement')
21
21
  const { blurElement } = require('./scripts/blurElement')
22
22
  const { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } = require('./errors/ElementAssertion')
23
23
  const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
24
+ const WebElement = require('../element/WebElement')
24
25
 
25
26
  const SHADOW = 'shadow'
26
27
  const webRoot = 'body'
@@ -936,7 +937,20 @@ class WebDriver extends Helper {
936
937
  *
937
938
  */
938
939
  async grabWebElements(locator) {
939
- return this._locate(locator)
940
+ const elements = await this._locate(locator)
941
+ return elements.map(element => new WebElement(element, this))
942
+ }
943
+
944
+ /**
945
+ * {{> grabWebElement }}
946
+ *
947
+ */
948
+ async grabWebElement(locator) {
949
+ const elements = await this._locate(locator)
950
+ if (elements.length === 0) {
951
+ throw new ElementNotFound(locator, 'Element')
952
+ }
953
+ return new WebElement(elements[0], this)
940
954
  }
941
955
 
942
956
  /**
@@ -981,7 +995,7 @@ class WebDriver extends Helper {
981
995
  * {{ react }}
982
996
  */
983
997
  async click(locator, context = null) {
984
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick'
998
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
985
999
  const locateFn = prepareLocateFn.call(this, context)
986
1000
 
987
1001
  const res = await findClickable.call(this, locator, locateFn)
@@ -1200,7 +1214,7 @@ class WebDriver extends Helper {
1200
1214
  * {{> checkOption }}
1201
1215
  */
1202
1216
  async checkOption(field, context = null) {
1203
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick'
1217
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1204
1218
  const locateFn = prepareLocateFn.call(this, context)
1205
1219
 
1206
1220
  const res = await findCheckable.call(this, field, locateFn)
@@ -1220,7 +1234,7 @@ class WebDriver extends Helper {
1220
1234
  * {{> uncheckOption }}
1221
1235
  */
1222
1236
  async uncheckOption(field, context = null) {
1223
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick'
1237
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1224
1238
  const locateFn = prepareLocateFn.call(this, context)
1225
1239
 
1226
1240
  const res = await findCheckable.call(this, field, locateFn)
@@ -0,0 +1,85 @@
1
+ const event = require('../event')
2
+ const { enhanceMochaTest } = require('../mocha/test')
3
+
4
+ /**
5
+ * Enhance retried tests by copying CodeceptJS-specific properties from the original test
6
+ * This fixes the issue where Mocha's shallow clone during retries loses CodeceptJS properties
7
+ */
8
+ module.exports = function () {
9
+ event.dispatcher.on(event.test.before, test => {
10
+ // Check if this test is a retry (has a reference to the original test)
11
+ const originalTest = test.retriedTest && test.retriedTest()
12
+
13
+ if (originalTest) {
14
+ // This is a retried test - copy CodeceptJS-specific properties from the original
15
+ copyCodeceptJSProperties(originalTest, test)
16
+
17
+ // Ensure the test is enhanced with CodeceptJS functionality
18
+ enhanceMochaTest(test)
19
+ }
20
+ })
21
+ }
22
+
23
+ /**
24
+ * Copy CodeceptJS-specific properties from the original test to the retried test
25
+ * @param {CodeceptJS.Test} originalTest - The original test object
26
+ * @param {CodeceptJS.Test} retriedTest - The retried test object
27
+ */
28
+ function copyCodeceptJSProperties(originalTest, retriedTest) {
29
+ // Copy CodeceptJS-specific properties
30
+ if (originalTest.opts !== undefined) {
31
+ retriedTest.opts = originalTest.opts ? { ...originalTest.opts } : {}
32
+ }
33
+
34
+ if (originalTest.tags !== undefined) {
35
+ retriedTest.tags = originalTest.tags ? [...originalTest.tags] : []
36
+ }
37
+
38
+ if (originalTest.notes !== undefined) {
39
+ retriedTest.notes = originalTest.notes ? [...originalTest.notes] : []
40
+ }
41
+
42
+ if (originalTest.meta !== undefined) {
43
+ retriedTest.meta = originalTest.meta ? { ...originalTest.meta } : {}
44
+ }
45
+
46
+ if (originalTest.artifacts !== undefined) {
47
+ retriedTest.artifacts = originalTest.artifacts ? [...originalTest.artifacts] : []
48
+ }
49
+
50
+ if (originalTest.steps !== undefined) {
51
+ retriedTest.steps = originalTest.steps ? [...originalTest.steps] : []
52
+ }
53
+
54
+ if (originalTest.config !== undefined) {
55
+ retriedTest.config = originalTest.config ? { ...originalTest.config } : {}
56
+ }
57
+
58
+ if (originalTest.inject !== undefined) {
59
+ retriedTest.inject = originalTest.inject ? { ...originalTest.inject } : {}
60
+ }
61
+
62
+ // Copy methods that might be missing
63
+ if (originalTest.addNote && !retriedTest.addNote) {
64
+ retriedTest.addNote = function (type, note) {
65
+ this.notes = this.notes || []
66
+ this.notes.push({ type, text: note })
67
+ }
68
+ }
69
+
70
+ if (originalTest.applyOptions && !retriedTest.applyOptions) {
71
+ retriedTest.applyOptions = originalTest.applyOptions.bind(retriedTest)
72
+ }
73
+
74
+ if (originalTest.simplify && !retriedTest.simplify) {
75
+ retriedTest.simplify = originalTest.simplify.bind(retriedTest)
76
+ }
77
+
78
+ // Preserve the uid if it exists
79
+ if (originalTest.uid !== undefined) {
80
+ retriedTest.uid = originalTest.uid
81
+ }
82
+
83
+ // Mark as enhanced
84
+ retriedTest.codeceptjs = true
85
+ }
@@ -3,10 +3,14 @@ const event = require('../event')
3
3
  const store = require('../store')
4
4
  const output = require('../output')
5
5
  const { BeforeHook, AfterHook, BeforeSuiteHook, AfterSuiteHook } = require('../mocha/hooks')
6
+ const recorder = require('../recorder')
6
7
 
7
8
  let currentTest
8
9
  let currentHook
9
10
 
11
+ // Session names that should not contribute steps to the main test trace
12
+ const EXCLUDED_SESSIONS = ['tryTo', 'hopeThat']
13
+
10
14
  /**
11
15
  * Register steps inside tests
12
16
  */
@@ -75,6 +79,14 @@ module.exports = function () {
75
79
  return currentHook.steps.push(step)
76
80
  }
77
81
  if (!currentTest || !currentTest.steps) return
82
+
83
+ // Check if we're in a session that should be excluded from main test steps
84
+ const currentSessionId = recorder.getCurrentSessionId()
85
+ if (currentSessionId && EXCLUDED_SESSIONS.includes(currentSessionId)) {
86
+ // Skip adding this step to the main test steps
87
+ return
88
+ }
89
+
78
90
  currentTest.steps.push(step)
79
91
  })
80
92
 
@@ -121,9 +121,19 @@ module.exports.injected = function (fn, suite, hookName) {
121
121
  const errHandler = err => {
122
122
  recorder.session.start('teardown')
123
123
  recorder.cleanAsyncErr()
124
- if (hookName == 'before' || hookName == 'beforeSuite') suiteTestFailedHookError(suite, err, hookName)
125
- if (hookName === 'after') suite.eachTest(test => event.emit(event.test.after, test))
126
- if (hookName === 'afterSuite') event.emit(event.suite.after, suite)
124
+ if (['before', 'beforeSuite'].includes(hookName)) {
125
+ suiteTestFailedHookError(suite, err, hookName)
126
+ }
127
+ if (hookName === 'after') {
128
+ suiteTestFailedHookError(suite, err, hookName)
129
+ suite.eachTest(test => {
130
+ event.emit(event.test.after, test)
131
+ })
132
+ }
133
+ if (hookName === 'afterSuite') {
134
+ suiteTestFailedHookError(suite, err, hookName)
135
+ event.emit(event.suite.after, suite)
136
+ }
127
137
  recorder.add(() => doneFn(err))
128
138
  }
129
139
 
package/lib/mocha/cli.js CHANGED
@@ -200,7 +200,7 @@ class Cli extends Base {
200
200
 
201
201
  // explicitly show file with error
202
202
  if (test.file) {
203
- log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('File:')} ${output.styles.basic(test.file)}\n`
203
+ log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('File:')} file://${test.file}\n`
204
204
  }
205
205
 
206
206
  const steps = test.steps || (test.ctx && test.ctx.test.steps)
@@ -107,7 +107,7 @@ module.exports = (text, file) => {
107
107
  )
108
108
  continue
109
109
  }
110
- if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline.includes(child.scenario.keyword) : child.scenario.keyword === 'Scenario Outline')) {
110
+ if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline === child.scenario.keyword : child.scenario.keyword === 'Scenario Outline')) {
111
111
  for (const examples of child.scenario.examples) {
112
112
  const fields = examples.tableHeader.cells.map(c => c.value)
113
113
  for (const example of examples.tableBody) {
package/lib/mocha/test.js CHANGED
@@ -77,6 +77,12 @@ function deserializeTest(test) {
77
77
  test.parent = Object.assign(new Suite(test.parent?.title || 'Suite'), test.parent)
78
78
  enhanceMochaSuite(test.parent)
79
79
  if (test.steps) test.steps = test.steps.map(step => Object.assign(new Step(step.title), step))
80
+
81
+ // Restore the custom fullTitle function to maintain consistency with original test
82
+ if (test.parent) {
83
+ test.fullTitle = () => `${test.parent.title}: ${test.title}`
84
+ }
85
+
80
86
  return test
81
87
  }
82
88
 
@@ -135,9 +141,19 @@ function cloneTest(test) {
135
141
  return deserializeTest(serializeTest(test))
136
142
  }
137
143
 
138
- function testToFileName(test, suffix = '') {
144
+ /**
145
+ * Get a filename from the test object
146
+ * @param {CodeceptJS.Test} test
147
+ * @param {Object} options
148
+ * @param {string} options.suffix Add a suffix to the filename
149
+ * @param {boolean} options.unique Add a unique suffix to the file
150
+ *
151
+ * @returns {string} the filename
152
+ */
153
+ function testToFileName(test, { suffix = '', unique = false } = {}) {
139
154
  let fileName = test.title
140
155
 
156
+ if (unique) fileName = `${fileName}_${test?.uid || Math.floor(new Date().getTime() / 1000)}`
141
157
  if (suffix) fileName = `${fileName}_${suffix}`
142
158
  // remove tags with empty string (disable for now)
143
159
  // fileName = fileName.replace(/\@\w+/g, '')
@@ -151,6 +167,7 @@ function testToFileName(test, suffix = '') {
151
167
  // fileName = `${clearString(test.parent.title)}_${fileName}`
152
168
  // }
153
169
  fileName = clearString(fileName).slice(0, 100)
170
+
154
171
  return fileName
155
172
  }
156
173
 
package/lib/mocha/ui.js CHANGED
@@ -103,6 +103,19 @@ module.exports = function (suite) {
103
103
  return new FeatureConfig(suite)
104
104
  }
105
105
 
106
+ /**
107
+ * Exclusive test suite - runs only this feature.
108
+ * @global
109
+ * @kind constant
110
+ * @type {CodeceptJS.IFeature}
111
+ */
112
+ context.Feature.only = function (title, opts) {
113
+ const reString = `^${escapeRe(`${title}:`)}`
114
+ mocha.grep(new RegExp(reString))
115
+ process.env.FEATURE_ONLY = true
116
+ return context.Feature(title, opts)
117
+ }
118
+
106
119
  /**
107
120
  * Pending test suite.
108
121
  * @global
package/lib/output.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const colors = require('chalk')
2
2
  const figures = require('figures')
3
- const { maskSensitiveData } = require('invisi-data')
3
+ const { maskData, shouldMaskData, getMaskConfig } = require('./utils/mask_data')
4
4
 
5
5
  const styles = {
6
6
  error: colors.bgRed.white.bold,
@@ -59,7 +59,7 @@ module.exports = {
59
59
  * @param {string} msg
60
60
  */
61
61
  debug(msg) {
62
- const _msg = isMaskedData() ? maskSensitiveData(msg) : msg
62
+ const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
63
63
  if (outputLevel >= 2) {
64
64
  print(' '.repeat(this.stepShift), styles.debug(`${figures.pointerSmall} ${_msg}`))
65
65
  }
@@ -70,7 +70,7 @@ module.exports = {
70
70
  * @param {string} msg
71
71
  */
72
72
  log(msg) {
73
- const _msg = isMaskedData() ? maskSensitiveData(msg) : msg
73
+ const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
74
74
  if (outputLevel >= 3) {
75
75
  print(' '.repeat(this.stepShift), styles.log(truncate(` ${_msg}`, this.spaceShift)))
76
76
  }
@@ -81,7 +81,8 @@ module.exports = {
81
81
  * @param {string} msg
82
82
  */
83
83
  error(msg) {
84
- print(styles.error(msg))
84
+ const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
85
+ print(styles.error(_msg))
85
86
  },
86
87
 
87
88
  /**
@@ -89,7 +90,8 @@ module.exports = {
89
90
  * @param {string} msg
90
91
  */
91
92
  success(msg) {
92
- print(styles.success(msg))
93
+ const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
94
+ print(styles.success(_msg))
93
95
  },
94
96
 
95
97
  /**
@@ -124,7 +126,7 @@ module.exports = {
124
126
  stepLine += colors.grey(step.comment.split('\n').join('\n' + ' '.repeat(4)))
125
127
  }
126
128
 
127
- const _stepLine = isMaskedData() ? maskSensitiveData(stepLine) : stepLine
129
+ const _stepLine = shouldMaskData() ? maskData(stepLine, getMaskConfig()) : stepLine
128
130
  print(' '.repeat(this.stepShift), truncate(_stepLine, this.spaceShift))
129
131
  },
130
132
 
@@ -278,7 +280,3 @@ function truncate(msg, gap = 0) {
278
280
  }
279
281
  return msg
280
282
  }
281
-
282
- function isMaskedData() {
283
- return global.maskSensitiveData === true || false
284
- }
package/lib/pause.js CHANGED
@@ -175,7 +175,12 @@ async function parseInput(cmd) {
175
175
  output.print(output.styles.success(' OK '), cmd)
176
176
  }
177
177
  if (cmd?.startsWith('I.grab')) {
178
- output.print(output.styles.debug(val))
178
+ try {
179
+ output.print(output.styles.debug(JSON.stringify(val, null, 2)))
180
+ } catch (err) {
181
+ output.print(output.styles.error(' ERROR '), 'Failed to stringify result:', err.message)
182
+ output.print(output.styles.error(' RAW VALUE '), String(val))
183
+ }
179
184
  }
180
185
 
181
186
  history.push(cmd) // add command to history when successful
@@ -7,7 +7,7 @@ let currentCommentStep
7
7
  const defaultGlobalName = '__'
8
8
 
9
9
  /**
10
- * @deprecated
10
+ * This plugin is **deprecated**, use `Section` instead.
11
11
  *
12
12
  * Add descriptive nested steps for your tests:
13
13
  *