codeceptjs 4.0.1-beta.25 → 4.0.1-beta.26

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.
@@ -1617,30 +1617,33 @@ class Puppeteer extends Helper {
1617
1617
  * {{> selectOption }}
1618
1618
  */
1619
1619
  async selectOption(select, option) {
1620
- const context = await this._getContext()
1621
- const matchedLocator = new Locator(select)
1622
-
1623
- // Strict locator
1624
- if (!matchedLocator.isFuzzy()) {
1625
- this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
1626
- const els = await this._locate(matchedLocator)
1627
- assertElementExists(els, select, 'Selectable element')
1628
- return proceedSelect.call(this, context, els[0], option)
1620
+ const els = await findVisibleFields.call(this, select)
1621
+ assertElementExists(els, select, 'Selectable field')
1622
+ const el = els[0]
1623
+ if ((await el.getProperty('tagName').then(t => t.jsonValue())) !== 'SELECT') {
1624
+ throw new Error('Element is not <select>')
1629
1625
  }
1626
+ highlightActiveElement.call(this, els[0], await this._getContext())
1627
+ if (!Array.isArray(option)) option = [option]
1628
+
1629
+ for (const key in option) {
1630
+ const opt = xpathLocator.literal(option[key])
1631
+ let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
1632
+ if (optEl.length) {
1633
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1634
+ continue
1635
+ }
1636
+ optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
1637
+ if (optEl.length) {
1638
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1639
+ }
1640
+ }
1641
+ await this._evaluateHandeInContext(element => {
1642
+ element.dispatchEvent(new Event('input', { bubbles: true }))
1643
+ element.dispatchEvent(new Event('change', { bubbles: true }))
1644
+ }, el)
1630
1645
 
1631
- // Fuzzy: try combobox
1632
- this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
1633
- let els = await findByRole.call(this, context, { role: 'combobox', name: matchedLocator.value })
1634
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
1635
-
1636
- // Fuzzy: try listbox
1637
- els = await findByRole.call(this, context, { role: 'listbox', name: matchedLocator.value })
1638
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
1639
-
1640
- // Fuzzy: try native select
1641
- els = await findVisibleFields.call(this, select)
1642
- assertElementExists(els, select, 'Selectable element')
1643
- return proceedSelect.call(this, context, els[0], option)
1646
+ return this._waitForAction()
1644
1647
  }
1645
1648
 
1646
1649
  /**
@@ -1690,10 +1693,6 @@ class Puppeteer extends Helper {
1690
1693
  * {{ react }}
1691
1694
  */
1692
1695
  async see(text, context = null) {
1693
- // If only one argument passed and it's an object without custom toString(), treat as locator
1694
- if (!context && text && typeof text === 'object' && !Array.isArray(text) && text.toString === Object.prototype.toString) {
1695
- return this.seeElement(text)
1696
- }
1697
1696
  return proceedSee.call(this, 'assert', text, context)
1698
1697
  }
1699
1698
 
@@ -2956,7 +2955,7 @@ async function findElements(matcher, locator) {
2956
2955
  async function findElement(matcher, locator) {
2957
2956
  if (locator.react) return findReactElements.call(this, locator)
2958
2957
  locator = new Locator(locator, 'css')
2959
-
2958
+
2960
2959
  // Check if locator is a role locator and call findByRole
2961
2960
  if (locator.isRole()) {
2962
2961
  const elements = await findByRole.call(this, matcher, locator)
@@ -2968,10 +2967,13 @@ async function findElement(matcher, locator) {
2968
2967
  const elements = await matcher.$$(locator.simplify())
2969
2968
  return elements[0]
2970
2969
  }
2971
-
2972
- // For XPath in Puppeteer 24.x+, use the same approach as findElements
2973
- // $x method was removed, so we use ::-p-xpath() or fallback
2974
- const elements = await findElements.call(this, matcher, locator)
2970
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2971
+ if (puppeteer.default?.defaultBrowserRevision) {
2972
+ const elements = await matcher.$$(`xpath/${locator.value}`)
2973
+ return elements[0]
2974
+ }
2975
+ // For Puppeteer 24.x+, $x method was removed - use ::-p-xpath() selector
2976
+ const elements = await matcher.$$(`::-p-xpath(${locator.value})`)
2975
2977
  return elements[0]
2976
2978
  }
2977
2979
 
@@ -3161,90 +3163,6 @@ async function findFields(locator) {
3161
3163
  return this._locate({ css: matchedLocator.value })
3162
3164
  }
3163
3165
 
3164
- async function proceedSelect(context, el, option) {
3165
- const role = await el.evaluate(e => e.getAttribute('role'))
3166
- const options = Array.isArray(option) ? option : [option]
3167
-
3168
- if (role === 'combobox') {
3169
- this.debugSection('SelectOption', 'Expanding combobox')
3170
- highlightActiveElement.call(this, el, context)
3171
- const [ariaOwns, ariaControls] = await el.evaluate(e => [e.getAttribute('aria-owns'), e.getAttribute('aria-controls')])
3172
- await el.click()
3173
- await this._waitForAction()
3174
-
3175
- const listboxId = ariaOwns || ariaControls
3176
- let listbox = listboxId ? await context.$(`#${listboxId}`) : null
3177
- if (!listbox) {
3178
- const listboxes = await context.$$('::-p-aria([role="listbox"])')
3179
- listbox = listboxes[0]
3180
- }
3181
- if (!listbox) throw new Error('Cannot find listbox for combobox')
3182
-
3183
- for (const opt of options) {
3184
- const optionEls = await listbox.$$('::-p-aria([role="option"])')
3185
- let optEl = null
3186
- for (const optionEl of optionEls) {
3187
- const text = await optionEl.evaluate(e => e.textContent.trim())
3188
- if (text === opt || text.includes(opt)) {
3189
- optEl = optionEl
3190
- break
3191
- }
3192
- }
3193
- if (!optEl) throw new Error(`Cannot find option "${opt}" in listbox`)
3194
- this.debugSection('SelectOption', `Clicking: "${opt}"`)
3195
- highlightActiveElement.call(this, optEl, context)
3196
- await optEl.click()
3197
- }
3198
- return this._waitForAction()
3199
- }
3200
-
3201
- if (role === 'listbox') {
3202
- highlightActiveElement.call(this, el, context)
3203
- for (const opt of options) {
3204
- const optionEls = await el.$$('::-p-aria([role="option"])')
3205
- let optEl = null
3206
- for (const optionEl of optionEls) {
3207
- const text = await optionEl.evaluate(e => e.textContent.trim())
3208
- if (text === opt || text.includes(opt)) {
3209
- optEl = optionEl
3210
- break
3211
- }
3212
- }
3213
- if (!optEl) throw new Error(`Cannot find option "${opt}" in listbox`)
3214
- this.debugSection('SelectOption', `Clicking: "${opt}"`)
3215
- highlightActiveElement.call(this, optEl, context)
3216
- await optEl.click()
3217
- }
3218
- return this._waitForAction()
3219
- }
3220
-
3221
- // Native <select> element
3222
- const tagName = await el.evaluate(e => e.tagName)
3223
- if (tagName !== 'SELECT') {
3224
- throw new Error('Element is not <select>')
3225
- }
3226
-
3227
- highlightActiveElement.call(this, el, context)
3228
- for (const key in options) {
3229
- const opt = xpathLocator.literal(options[key])
3230
- let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
3231
- if (optEl.length) {
3232
- this._evaluateHandeInContext(e => (e.selected = true), optEl[0])
3233
- continue
3234
- }
3235
- optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
3236
- if (optEl.length) {
3237
- this._evaluateHandeInContext(e => (e.selected = true), optEl[0])
3238
- }
3239
- }
3240
- await this._evaluateHandeInContext(element => {
3241
- element.dispatchEvent(new Event('input', { bubbles: true }))
3242
- element.dispatchEvent(new Event('change', { bubbles: true }))
3243
- }, el)
3244
-
3245
- return this._waitForAction()
3246
- }
3247
-
3248
3166
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
3249
3167
  const src = await this._locateElement(sourceLocator)
3250
3168
  if (!src) {
@@ -1302,29 +1302,33 @@ class WebDriver extends Helper {
1302
1302
  * {{> selectOption }}
1303
1303
  */
1304
1304
  async selectOption(select, option) {
1305
- const matchedLocator = new Locator(select)
1305
+ const res = await findFields.call(this, select)
1306
+ assertElementExists(res, select, 'Selectable field')
1307
+ const elem = usingFirstElement(res)
1308
+ highlightActiveElement.call(this, elem)
1306
1309
 
1307
- // Strict locator
1308
- if (!matchedLocator.isFuzzy()) {
1309
- this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
1310
- const els = await this._locate(matchedLocator)
1311
- assertElementExists(els, select, 'Selectable element')
1312
- return proceedSelect.call(this, els[0], option)
1310
+ if (!Array.isArray(option)) {
1311
+ option = [option]
1313
1312
  }
1314
1313
 
1315
- // Fuzzy: try combobox
1316
- this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
1317
- let els = await this._locateByRole({ role: 'combobox', text: matchedLocator.value })
1318
- if (els?.length) return proceedSelect.call(this, els[0], option)
1314
+ // select options by visible text
1315
+ let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
1319
1316
 
1320
- // Fuzzy: try listbox
1321
- els = await this._locateByRole({ role: 'listbox', text: matchedLocator.value })
1322
- if (els?.length) return proceedSelect.call(this, els[0], option)
1317
+ const clickOptionFn = async el => {
1318
+ if (el[0]) el = el[0]
1319
+ const elementId = getElementId(el)
1320
+ if (elementId) return this.browser.elementClick(elementId)
1321
+ }
1323
1322
 
1324
- // Fuzzy: try native select
1325
- els = await findFields.call(this, select)
1326
- assertElementExists(els, select, 'Selectable element')
1327
- return proceedSelect.call(this, els[0], option)
1323
+ if (Array.isArray(els) && els.length) {
1324
+ return forEachAsync(els, clickOptionFn)
1325
+ }
1326
+ // select options by value
1327
+ els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
1328
+ if (els.length === 0) {
1329
+ throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
1330
+ }
1331
+ return forEachAsync(els, clickOptionFn)
1328
1332
  }
1329
1333
 
1330
1334
  /**
@@ -1563,10 +1567,6 @@ class WebDriver extends Helper {
1563
1567
  * {{ react }}
1564
1568
  */
1565
1569
  async see(text, context = null) {
1566
- // If only one argument passed and it's an object without custom toString(), treat as locator
1567
- if (!context && text && typeof text === 'object' && !Array.isArray(text) && text.toString === Object.prototype.toString) {
1568
- return this.seeElement(text)
1569
- }
1570
1570
  return proceedSee.call(this, 'assert', text, context)
1571
1571
  }
1572
1572
 
@@ -2403,10 +2403,6 @@ class WebDriver extends Helper {
2403
2403
  })
2404
2404
  }
2405
2405
 
2406
- async _waitForAction() {
2407
- return this.wait(0.1)
2408
- }
2409
-
2410
2406
  /**
2411
2407
  * {{> waitForEnabled }}
2412
2408
  */
@@ -3022,94 +3018,6 @@ async function findFields(locator) {
3022
3018
  return await this._locate(locator.value) // by css or xpath
3023
3019
  }
3024
3020
 
3025
- async function proceedSelect(el, option) {
3026
- const elementId = getElementId(el)
3027
- const role = await this.browser.getElementAttribute(elementId, 'role')
3028
- const options = Array.isArray(option) ? option : [option]
3029
-
3030
- if (role === 'combobox') {
3031
- this.debugSection('SelectOption', 'Expanding combobox')
3032
- highlightActiveElement.call(this, el)
3033
- const ariaOwns = await this.browser.getElementAttribute(elementId, 'aria-owns')
3034
- const ariaControls = await this.browser.getElementAttribute(elementId, 'aria-controls')
3035
- await this.browser.elementClick(elementId)
3036
- await this._waitForAction()
3037
-
3038
- const listboxId = ariaOwns || ariaControls
3039
- let listboxEls = listboxId ? await this.browser.$$(`#${listboxId}`) : []
3040
- if (!listboxEls.length) {
3041
- listboxEls = await this.browser.findElementsFromElement(elementId, 'xpath', 'following-sibling::*[@role="listbox"]')
3042
- }
3043
- if (!listboxEls.length) {
3044
- listboxEls = await this.browser.findElementsFromElement(elementId, 'xpath', 'ancestor::*[@role="listbox"]')
3045
- }
3046
- if (!listboxEls.length) throw new Error('Cannot find listbox for combobox')
3047
- const listbox = listboxEls[0]
3048
- const listboxElId = getElementId(listbox)
3049
-
3050
- for (const opt of options) {
3051
- const optionEls = await this.browser.findElementsFromElement(listboxElId, 'css selector', '[role="option"]')
3052
- let optEl = null
3053
- for (const optionEl of optionEls) {
3054
- const optElId = getElementId(optionEl)
3055
- const text = await this.browser.getElementText(optElId)
3056
- if (text === opt || (text && text.includes(opt))) {
3057
- optEl = optionEl
3058
- break
3059
- }
3060
- }
3061
- if (!optEl) throw new Error(`Cannot find option "${opt}" in listbox`)
3062
- this.debugSection('SelectOption', `Clicking: "${opt}"`)
3063
- highlightActiveElement.call(this, optEl)
3064
- await this.browser.elementClick(getElementId(optEl))
3065
- }
3066
- return this._waitForAction()
3067
- }
3068
-
3069
- if (role === 'listbox') {
3070
- highlightActiveElement.call(this, el)
3071
- for (const opt of options) {
3072
- const optionEls = await this.browser.findElementsFromElement(elementId, 'css selector', '[role="option"]')
3073
- let optEl = null
3074
- for (const optionEl of optionEls) {
3075
- const optElId = getElementId(optionEl)
3076
- const text = await this.browser.getElementText(optElId)
3077
- if (text === opt || (text && text.includes(opt))) {
3078
- optEl = optionEl
3079
- break
3080
- }
3081
- }
3082
- if (!optEl) throw new Error(`Cannot find option "${opt}" in listbox`)
3083
- this.debugSection('SelectOption', `Clicking: "${opt}"`)
3084
- highlightActiveElement.call(this, optEl)
3085
- await this.browser.elementClick(getElementId(optEl))
3086
- }
3087
- return this._waitForAction()
3088
- }
3089
-
3090
- // Native <select> element
3091
- highlightActiveElement.call(this, el)
3092
-
3093
- // select options by visible text
3094
- let els = await forEachAsync(options, async opt => this.browser.findElementsFromElement(elementId, 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
3095
-
3096
- const clickOptionFn = async optEl => {
3097
- if (optEl[0]) optEl = optEl[0]
3098
- const optElId = getElementId(optEl)
3099
- if (optElId) return this.browser.elementClick(optElId)
3100
- }
3101
-
3102
- if (Array.isArray(els) && els.length) {
3103
- return forEachAsync(els, clickOptionFn)
3104
- }
3105
- // select options by value
3106
- els = await forEachAsync(options, async opt => this.browser.findElementsFromElement(elementId, 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
3107
- if (els.length === 0) {
3108
- throw new ElementNotFound(el, `Option "${options}" in`, 'was not found neither by a visible text nor by a value')
3109
- }
3110
- return forEachAsync(els, clickOptionFn)
3111
- }
3112
-
3113
3021
  async function proceedSeeField(assertType, field, value) {
3114
3022
  const res = await findFields.call(this, field)
3115
3023
  assertElementExists(res, field, 'Field')
@@ -11,20 +11,22 @@ function buildLocatorString(locator) {
11
11
  }
12
12
 
13
13
  async function findElements(matcher, locator) {
14
- const matchedLocator = Locator.from(locator, 'css')
14
+ const matchedLocator = new Locator(locator, 'css')
15
15
 
16
16
  if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
17
17
  if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
18
+ if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator)
18
19
  if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator)
19
20
 
20
21
  return matcher.locator(buildLocatorString(matchedLocator)).all()
21
22
  }
22
23
 
23
24
  async function findElement(matcher, locator) {
24
- const matchedLocator = Locator.from(locator, 'css')
25
+ const matchedLocator = new Locator(locator, 'css')
25
26
 
26
27
  if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
27
28
  if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
29
+ if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator, { first: true })
28
30
  if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator, { first: true })
29
31
 
30
32
  return matcher.locator(buildLocatorString(matchedLocator)).first()
@@ -44,30 +46,49 @@ async function getVisibleElements(elements) {
44
46
  }
45
47
 
46
48
  async function findReact(matcher, locator) {
47
- const props = locator.locator?.props
48
- let locatorString = `_react=${locator.value}`
49
+ const details = locator.locator ?? { react: locator.value }
50
+ let locatorString = `_react=${details.react}`
49
51
 
50
- if (props) {
51
- locatorString += propBuilder(props)
52
+ if (details.props) {
53
+ locatorString += propBuilder(details.props)
52
54
  }
53
55
 
54
56
  return matcher.locator(locatorString).all()
55
57
  }
56
58
 
57
59
  async function findVue(matcher, locator) {
58
- const props = locator.locator?.props
59
- let locatorString = `_vue=${locator.value}`
60
+ const details = locator.locator ?? { vue: locator.value }
61
+ let locatorString = `_vue=${details.vue}`
60
62
 
61
- if (props) {
62
- locatorString += propBuilder(props)
63
+ if (details.props) {
64
+ locatorString += propBuilder(details.props)
63
65
  }
64
66
 
65
67
  return matcher.locator(locatorString).all()
66
68
  }
67
69
 
70
+ async function findByPlaywrightLocator(matcher, locator, { first = false } = {}) {
71
+ const details = locator.locator ?? { pw: locator.value }
72
+ const locatorValue = details.pw
73
+
74
+ const handle = matcher.locator(locatorValue)
75
+ return first ? handle.first() : handle.all()
76
+ }
77
+
68
78
  async function findByRole(matcher, locator, { first = false } = {}) {
69
- const roleOptions = locator.getRoleOptions()
70
- const roleLocator = matcher.getByRole(roleOptions.role, roleOptions.options)
79
+ const details = locator.locator ?? { role: locator.value }
80
+ const { role, text, name, exact, includeHidden, ...rest } = details
81
+ const options = { ...rest }
82
+
83
+ if (includeHidden !== undefined) options.includeHidden = includeHidden
84
+
85
+ const accessibleName = name ?? text
86
+ if (accessibleName !== undefined) {
87
+ options.name = accessibleName
88
+ if (exact === true) options.exact = true
89
+ }
90
+
91
+ const roleLocator = matcher.getByRole(role, options)
71
92
  return first ? roleLocator.first() : roleLocator.all()
72
93
  }
73
94
 
@@ -86,4 +107,4 @@ function propBuilder(props) {
86
107
  return _props
87
108
  }
88
109
 
89
- export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByRole }
110
+ export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByPlaywrightLocator, findByRole }
@@ -0,0 +1,52 @@
1
+ async function findReact(matcher, locator) {
2
+ // Handle both Locator objects and raw locator objects
3
+ const reactLocator = locator.locator || locator
4
+ let _locator = `_react=${reactLocator.react}`;
5
+ let props = '';
6
+
7
+ if (reactLocator.props) {
8
+ props += propBuilder(reactLocator.props);
9
+ _locator += props;
10
+ }
11
+ return matcher.locator(_locator).all();
12
+ }
13
+
14
+ async function findVue(matcher, locator) {
15
+ // Handle both Locator objects and raw locator objects
16
+ const vueLocator = locator.locator || locator
17
+ let _locator = `_vue=${vueLocator.vue}`;
18
+ let props = '';
19
+
20
+ if (vueLocator.props) {
21
+ props += propBuilder(vueLocator.props);
22
+ _locator += props;
23
+ }
24
+ return matcher.locator(_locator).all();
25
+ }
26
+
27
+ async function findByPlaywrightLocator(matcher, locator) {
28
+ // Handle both Locator objects and raw locator objects
29
+ const pwLocator = locator.locator || locator
30
+ if (pwLocator && pwLocator.toString && pwLocator.toString().includes(process.env.testIdAttribute)) {
31
+ return matcher.getByTestId(pwLocator.pw.value.split('=')[1]);
32
+ }
33
+ const pwValue = typeof pwLocator.pw === 'string' ? pwLocator.pw : pwLocator.pw
34
+ return matcher.locator(pwValue).all();
35
+ }
36
+
37
+ function propBuilder(props) {
38
+ let _props = '';
39
+
40
+ for (const [key, value] of Object.entries(props)) {
41
+ if (typeof value === 'object') {
42
+ for (const [k, v] of Object.entries(value)) {
43
+ _props += `[${key}.${k} = "${v}"]`;
44
+ }
45
+ } else {
46
+ _props += `[${key} = "${value}"]`;
47
+ }
48
+ }
49
+ return _props;
50
+ }
51
+
52
+ export { findReact, findVue, findByPlaywrightLocator };