codeceptjs 4.0.0-rc.15 → 4.0.0-rc.16

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.
@@ -155,7 +155,6 @@ export default async function (options) {
155
155
  checks.teardown = true
156
156
  for (const helper of Object.values(helpers).reverse()) {
157
157
  try {
158
- if (helper._passed) await helper._passed(test)
159
158
  if (helper._after) await helper._after(test)
160
159
  if (helper._finishTest) await helper._finishTest(suite)
161
160
  if (helper._afterSuite) await helper._afterSuite(suite)
@@ -7,13 +7,13 @@ const EDITOR = {
7
7
  IFRAME: 'iframe',
8
8
  CONTENTEDITABLE: 'contenteditable',
9
9
  HIDDEN_TEXTAREA: 'hidden-textarea',
10
+ UNREACHABLE: 'unreachable',
10
11
  }
11
12
 
12
13
  function detectAndMark(el, opts) {
13
14
  const marker = opts.marker
14
15
  const kinds = opts.kinds
15
16
  const CE = '[contenteditable="true"], [contenteditable=""]'
16
- const MAX_HIDDEN_ASCENT = 3
17
17
 
18
18
  function mark(kind, target) {
19
19
  document.querySelectorAll('[' + marker + ']').forEach(n => n.removeAttribute(marker))
@@ -27,38 +27,76 @@ function detectAndMark(el, opts) {
27
27
  if (tag === 'IFRAME') return mark(kinds.IFRAME, el)
28
28
  if (el.isContentEditable) return mark(kinds.CONTENTEDITABLE, el)
29
29
 
30
+ const isFormHidden = tag === 'INPUT' && el.type === 'hidden'
31
+ if ((tag === 'INPUT' || tag === 'TEXTAREA') && !isFormHidden) {
32
+ const style = window.getComputedStyle(el)
33
+ if (style.display === 'none') return mark(kinds.UNREACHABLE, el)
34
+ }
35
+
30
36
  const canSearchDescendants = tag !== 'INPUT' && tag !== 'TEXTAREA'
31
37
  if (canSearchDescendants) {
32
38
  const iframe = el.querySelector('iframe')
33
39
  if (iframe) return mark(kinds.IFRAME, iframe)
34
40
  const ce = el.querySelector(CE)
35
41
  if (ce) return mark(kinds.CONTENTEDITABLE, ce)
36
- const textarea = el.querySelector('textarea')
42
+ const textareas = [...el.querySelectorAll('textarea')]
43
+ const focusable = textareas.find(t => window.getComputedStyle(t).display !== 'none')
44
+ const textarea = focusable || textareas[0]
37
45
  if (textarea) return mark(kinds.HIDDEN_TEXTAREA, textarea)
38
46
  }
39
47
 
40
- const style = window.getComputedStyle(el)
41
- const isHidden =
42
- el.offsetParent === null ||
43
- (el.offsetWidth === 0 && el.offsetHeight === 0) ||
44
- style.display === 'none' ||
45
- style.visibility === 'hidden'
46
- if (!isHidden) return mark(kinds.STANDARD, el)
48
+ return mark(kinds.STANDARD, el)
49
+ }
50
+
51
+ function detectInsideFrame() {
52
+ const MARKER = 'data-codeceptjs-rte-target'
53
+ const CE = '[contenteditable="true"], [contenteditable=""]'
54
+ const CONTENTEDITABLE = 'contenteditable'
55
+ const HIDDEN_TEXTAREA = 'hidden-textarea'
56
+ const body = document.body
57
+ document.querySelectorAll('[' + MARKER + ']').forEach(n => n.removeAttribute(MARKER))
47
58
 
48
- const isFormHidden = tag === 'INPUT' && el.type === 'hidden'
49
- if (isFormHidden) return mark(kinds.STANDARD, el)
50
-
51
- let scope = el.parentElement
52
- for (let depth = 0; scope && depth < MAX_HIDDEN_ASCENT; depth++, scope = scope.parentElement) {
53
- const iframeNear = scope.querySelector('iframe')
54
- if (iframeNear) return mark(kinds.IFRAME, iframeNear)
55
- const ceNear = scope.querySelector(CE)
56
- if (ceNear) return mark(kinds.CONTENTEDITABLE, ceNear)
57
- const textareaNear = [...scope.querySelectorAll('textarea')].find(t => t !== el)
58
- if (textareaNear) return mark(kinds.HIDDEN_TEXTAREA, textareaNear)
59
+ if (body.isContentEditable) return CONTENTEDITABLE
60
+
61
+ const ce = body.querySelector(CE)
62
+ if (ce) {
63
+ ce.setAttribute(MARKER, '1')
64
+ return CONTENTEDITABLE
59
65
  }
60
66
 
61
- return mark(kinds.STANDARD, el)
67
+ const textareas = [...body.querySelectorAll('textarea')]
68
+ const focusable = textareas.find(t => window.getComputedStyle(t).display !== 'none')
69
+ const textarea = focusable || textareas[0]
70
+ if (textarea) {
71
+ textarea.setAttribute(MARKER, '1')
72
+ return HIDDEN_TEXTAREA
73
+ }
74
+
75
+ return CONTENTEDITABLE
76
+ }
77
+
78
+ async function evaluateInFrame(helper, body, fn) {
79
+ if (body.helperType === 'webdriver') {
80
+ return helper.executeScript(fn)
81
+ }
82
+ return body.element.evaluate(fn)
83
+ }
84
+
85
+ function focusMarkedInFrameScript() {
86
+ const el = document.querySelector('[data-codeceptjs-rte-target]') || document.body
87
+ el.focus()
88
+ return document.activeElement === el
89
+ }
90
+
91
+ function selectAllInFrameScript() {
92
+ const el = document.querySelector('[data-codeceptjs-rte-target]') || document.body
93
+ el.focus()
94
+ const range = document.createRange()
95
+ range.selectNodeContents(el)
96
+ const sel = window.getSelection()
97
+ sel.removeAllRanges()
98
+ sel.addRange(range)
99
+ return document.activeElement === el
62
100
  }
63
101
 
64
102
  function selectAllInEditable(el) {
@@ -76,6 +114,17 @@ function unmarkAll(marker) {
76
114
  document.querySelectorAll('[' + marker + ']').forEach(n => n.removeAttribute(marker))
77
115
  }
78
116
 
117
+ function isActive(el) {
118
+ return el.ownerDocument.activeElement === el
119
+ }
120
+
121
+ async function assertFocused(target) {
122
+ const focused = await target.evaluate(isActive)
123
+ if (!focused) {
124
+ throw new Error('fillField: rich editor target did not accept focus. Locator must point at the visible editor surface (a wrapper, iframe, or contenteditable) — not a hidden backing element.')
125
+ }
126
+ }
127
+
79
128
  async function findMarked(helper) {
80
129
  const root = helper.page || helper.browser
81
130
  const raw = await root.$('[' + MARKER + ']')
@@ -91,22 +140,36 @@ export async function fillRichEditor(helper, el, value) {
91
140
  const source = el instanceof WebElement ? el : new WebElement(el, helper)
92
141
  const kind = await source.evaluate(detectAndMark, { marker: MARKER, kinds: EDITOR })
93
142
  if (kind === EDITOR.STANDARD) return false
143
+ if (kind === EDITOR.UNREACHABLE) {
144
+ throw new Error('fillField: cannot fill a display:none form control. Locator must point at the visible editor surface (a wrapper, iframe, or contenteditable).')
145
+ }
94
146
 
95
147
  const target = await findMarked(helper)
96
148
  const delay = helper.options.pressKeyDelay
97
149
 
98
150
  if (kind === EDITOR.IFRAME) {
99
151
  await target.inIframe(async body => {
100
- await body.evaluate(selectAllInEditable)
101
- await body.typeText(value, { delay })
152
+ const innerKind = await evaluateInFrame(helper, body, detectInsideFrame)
153
+ if (innerKind === EDITOR.HIDDEN_TEXTAREA) {
154
+ const focused = await evaluateInFrame(helper, body, focusMarkedInFrameScript)
155
+ if (!focused) throw new Error('fillField: rich editor target inside iframe did not accept focus.')
156
+ await body.selectAllAndDelete()
157
+ await body.typeText(value, { delay })
158
+ } else {
159
+ const focused = await evaluateInFrame(helper, body, selectAllInFrameScript)
160
+ if (!focused) throw new Error('fillField: rich editor target inside iframe did not accept focus.')
161
+ await body.typeText(value, { delay })
162
+ }
102
163
  })
103
164
  } else if (kind === EDITOR.HIDDEN_TEXTAREA) {
104
165
  await target.focus()
166
+ await assertFocused(target)
105
167
  await target.selectAllAndDelete()
106
168
  await target.typeText(value, { delay })
107
169
  } else if (kind === EDITOR.CONTENTEDITABLE) {
108
170
  await target.click()
109
171
  await target.evaluate(selectAllInEditable)
172
+ await assertFocused(target)
110
173
  await target.typeText(value, { delay })
111
174
  }
112
175
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.0-rc.15",
3
+ "version": "4.0.0-rc.16",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [