@@ -7,6 +7,7 @@ const EDITOR = {
77 IFRAME : 'iframe' ,
88 CONTENTEDITABLE : 'contenteditable' ,
99 HIDDEN_TEXTAREA : 'hidden-textarea' ,
10+ UNREACHABLE : 'unreachable' ,
1011}
1112
1213function detectAndMark ( el , opts ) {
@@ -26,6 +27,12 @@ function detectAndMark(el, opts) {
2627 if ( tag === 'IFRAME' ) return mark ( kinds . IFRAME , el )
2728 if ( el . isContentEditable ) return mark ( kinds . CONTENTEDITABLE , el )
2829
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+
2936 const canSearchDescendants = tag !== 'INPUT' && tag !== 'TEXTAREA'
3037 if ( canSearchDescendants ) {
3138 const iframe = el . querySelector ( 'iframe' )
@@ -41,29 +48,55 @@ function detectAndMark(el, opts) {
4148 return mark ( kinds . STANDARD , el )
4249}
4350
44- function detectInsideFrame ( body , opts ) {
45- const marker = opts . marker
46- const kinds = opts . kinds
51+ function detectInsideFrame ( ) {
52+ const MARKER = 'data-codeceptjs-rte-target'
4753 const CE = '[contenteditable="true"], [contenteditable=""]'
48- body . ownerDocument . querySelectorAll ( '[' + marker + ']' ) . forEach ( n => n . removeAttribute ( marker ) )
54+ const CONTENTEDITABLE = 'contenteditable'
55+ const HIDDEN_TEXTAREA = 'hidden-textarea'
56+ const body = document . body
57+ document . querySelectorAll ( '[' + MARKER + ']' ) . forEach ( n => n . removeAttribute ( MARKER ) )
4958
50- if ( body . isContentEditable ) return kinds . CONTENTEDITABLE
59+ if ( body . isContentEditable ) return CONTENTEDITABLE
5160
5261 const ce = body . querySelector ( CE )
5362 if ( ce ) {
54- ce . setAttribute ( marker , '1' )
55- return kinds . CONTENTEDITABLE
63+ ce . setAttribute ( MARKER , '1' )
64+ return CONTENTEDITABLE
5665 }
5766
5867 const textareas = [ ...body . querySelectorAll ( 'textarea' ) ]
59- const focusable = textareas . find ( t => body . ownerDocument . defaultView . getComputedStyle ( t ) . display !== 'none' )
68+ const focusable = textareas . find ( t => window . getComputedStyle ( t ) . display !== 'none' )
6069 const textarea = focusable || textareas [ 0 ]
6170 if ( textarea ) {
62- textarea . setAttribute ( marker , '1' )
63- return kinds . HIDDEN_TEXTAREA
71+ textarea . setAttribute ( MARKER , '1' )
72+ return HIDDEN_TEXTAREA
6473 }
6574
66- return kinds . CONTENTEDITABLE
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
67100}
68101
69102function selectAllInEditable ( el ) {
@@ -107,24 +140,25 @@ export async function fillRichEditor(helper, el, value) {
107140 const source = el instanceof WebElement ? el : new WebElement ( el , helper )
108141 const kind = await source . evaluate ( detectAndMark , { marker : MARKER , kinds : EDITOR } )
109142 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+ }
110146
111147 const target = await findMarked ( helper )
112148 const delay = helper . options . pressKeyDelay
113149
114150 if ( kind === EDITOR . IFRAME ) {
115151 await target . inIframe ( async body => {
116- const innerKind = await body . evaluate ( detectInsideFrame , { marker : MARKER , kinds : EDITOR } )
117- const marked = await body . $ ( '[' + MARKER + ']' )
118- const innerTarget = marked || body
152+ const innerKind = await evaluateInFrame ( helper , body , detectInsideFrame )
119153 if ( innerKind === EDITOR . HIDDEN_TEXTAREA ) {
120- await innerTarget . focus ( )
121- await assertFocused ( innerTarget )
122- await innerTarget . selectAllAndDelete ( )
123- await innerTarget . typeText ( value , { delay } )
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 } )
124158 } else {
125- await innerTarget . evaluate ( selectAllInEditable )
126- await assertFocused ( innerTarget )
127- await innerTarget . typeText ( value , { delay } )
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 } )
128162 }
129163 } )
130164 } else if ( kind === EDITOR . HIDDEN_TEXTAREA ) {
0 commit comments