How to use buildFocusableTree method in Testcafe

Best JavaScript code snippet using testcafe

Run Testcafe automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

dom.js

Source: dom.js Github

copy
1import hammerhead from '../deps/hammerhead';
2import * as arrayUtils from './array';
3
4const browserUtils  = hammerhead.utils.browser;
5const nativeMethods = hammerhead.nativeMethods;
6
7// NOTE: We have to retrieve styleUtils.get from hammerhead
8// to avoid circular dependencies between domUtils and styleUtils
9const getElementStyleProperty = hammerhead.utils.style.get;
10
11export const getActiveElement                       = hammerhead.utils.dom.getActiveElement;
12export const findDocument                           = hammerhead.utils.dom.findDocument;
13export const isElementInDocument                    = hammerhead.utils.dom.isElementInDocument;
14export const isElementInIframe                      = hammerhead.utils.dom.isElementInIframe;
15export const getIframeByElement                     = hammerhead.utils.dom.getIframeByElement;
16export const isCrossDomainWindows                   = hammerhead.utils.dom.isCrossDomainWindows;
17export const getSelectParent                        = hammerhead.utils.dom.getSelectParent;
18export const getChildVisibleIndex                   = hammerhead.utils.dom.getChildVisibleIndex;
19export const getSelectVisibleChildren               = hammerhead.utils.dom.getSelectVisibleChildren;
20export const isElementNode                          = hammerhead.utils.dom.isElementNode;
21export const isTextNode                             = hammerhead.utils.dom.isTextNode;
22export const isRenderedNode                         = hammerhead.utils.dom.isRenderedNode;
23export const isIframeElement                        = hammerhead.utils.dom.isIframeElement;
24export const isInputElement                         = hammerhead.utils.dom.isInputElement;
25export const isButtonElement                        = hammerhead.utils.dom.isButtonElement;
26export const isFileInput                            = hammerhead.utils.dom.isFileInput;
27export const isTextAreaElement                      = hammerhead.utils.dom.isTextAreaElement;
28export const isAnchorElement                        = hammerhead.utils.dom.isAnchorElement;
29export const isImgElement                           = hammerhead.utils.dom.isImgElement;
30export const isFormElement                          = hammerhead.utils.dom.isFormElement;
31export const isLabelElement                         = hammerhead.utils.dom.isLabelElement;
32export const isSelectElement                        = hammerhead.utils.dom.isSelectElement;
33export const isRadioButtonElement                   = hammerhead.utils.dom.isRadioButtonElement;
34export const isColorInputElement                    = hammerhead.utils.dom.isColorInputElement;
35export const isCheckboxElement                      = hammerhead.utils.dom.isCheckboxElement;
36export const isOptionElement                        = hammerhead.utils.dom.isOptionElement;
37export const isSVGElement                           = hammerhead.utils.dom.isSVGElement;
38export const isMapElement                           = hammerhead.utils.dom.isMapElement;
39export const isBodyElement                          = hammerhead.utils.dom.isBodyElement;
40export const isHtmlElement                          = hammerhead.utils.dom.isHtmlElement;
41export const isDocument                             = hammerhead.utils.dom.isDocument;
42export const isWindow                               = hammerhead.utils.dom.isWindow;
43export const isTextEditableInput                    = hammerhead.utils.dom.isTextEditableInput;
44export const isTextEditableElement                  = hammerhead.utils.dom.isTextEditableElement;
45export const isTextEditableElementAndEditingAllowed = hammerhead.utils.dom.isTextEditableElementAndEditingAllowed;
46export const isContentEditableElement               = hammerhead.utils.dom.isContentEditableElement;
47export const isDomElement                           = hammerhead.utils.dom.isDomElement;
48export const isShadowUIElement                      = hammerhead.utils.dom.isShadowUIElement;
49export const isShadowRoot                           = hammerhead.utils.dom.isShadowRoot;
50export const isElementFocusable                     = hammerhead.utils.dom.isElementFocusable;
51export const isHammerheadAttr                       = hammerhead.utils.dom.isHammerheadAttr;
52export const isElementReadOnly                      = hammerhead.utils.dom.isElementReadOnly;
53export const getScrollbarSize                       = hammerhead.utils.dom.getScrollbarSize;
54export const getMapContainer                        = hammerhead.utils.dom.getMapContainer;
55export const getTagName                             = hammerhead.utils.dom.getTagName;
56export const closest                                = hammerhead.utils.dom.closest;
57export const getParents                             = hammerhead.utils.dom.getParents;
58export const findParent                             = hammerhead.utils.dom.findParent;
59export const getTopSameDomainWindow                 = hammerhead.utils.dom.getTopSameDomainWindow;
60export const getParentExceptShadowRoot              = hammerhead.utils.dom.getParentExceptShadowRoot;
61
62function canFocus (element, parent, tabIndex) {
63    let activeElement = null;
64
65    if (parent.nodeType === Node.DOCUMENT_NODE)
66        activeElement = nativeMethods.documentActiveElementGetter.call(parent);
67
68    if (element === activeElement)
69        return true;
70
71    if (element.disabled)
72        return false;
73
74    if (getElementStyleProperty(element, 'display') === 'none' || getElementStyleProperty(element, 'visibility') === 'hidden')
75        return false;
76
77    if ((browserUtils.isIE || browserUtils.isAndroid) && isOptionElement(element))
78        return false;
79
80    if (tabIndex !== null && tabIndex < 0)
81        return false;
82
83    return true;
84}
85
86function wrapElement (el) {
87    return {
88        el:       el,
89        skip:     el.shadowRoot && el.tabIndex < 0,
90        children: {},
91    };
92}
93
94function buildFocusableTree (parent, sort) {
95    const node = wrapElement(parent);
96
97    parent = parent.shadowRoot || parent;
98
99    if (isIframeElement(parent))
100        parent = nativeMethods.contentDocumentGetter.call(parent);
101
102    if (parent && (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE || parent.nodeType === Node.DOCUMENT_NODE)) {
103        const elements = filterFocusableElements(parent);
104
105        for (const el of elements) {
106            const key = !sort || el.tabIndex <= 0 ? -1 : el.tabIndex;
107
108            node.children[key] = node.children[key] || [];
109
110            node.children[key].push(buildFocusableTree(el, sort));
111        }
112    }
113
114    return node;
115}
116
117function filterFocusableElements (parent) {
118    // NOTE: We don't take into account the case of embedded contentEditable
119    // elements and specify the contentEditable attribute for focusable elements
120    const allElements           = parent.querySelectorAll('*');
121    const invisibleElements     = getInvisibleElements(allElements);
122    const inputElementsRegExp   = /^(input|button|select|textarea)$/;
123    const focusableElements     = [];
124
125    let element  = null;
126    let tagName  = null;
127    let tabIndex = null;
128
129    let needPush = false;
130
131    for (let i = 0; i < allElements.length; i++) {
132        element  = allElements[i];
133        tagName  = getTagName(element);
134        tabIndex = getTabIndexAttributeIntValue(element);
135        needPush = false;
136
137        if (!canFocus(element, parent, tabIndex))
138            continue;
139
140        if (inputElementsRegExp.test(tagName))
141            needPush = true;
142        else if (element.shadowRoot)
143            needPush = true;
144        else if (isIframeElement(element))
145            needPush = true;
146        else if (isAnchorElement(element) && element.hasAttribute('href'))
147            needPush = element.getAttribute('href') !== '' || !browserUtils.isIE || tabIndex !== null;
148
149        const contentEditableAttr = element.getAttribute('contenteditable');
150
151        if (contentEditableAttr === '' || contentEditableAttr === 'true')
152            needPush = true;
153
154        if (tabIndex !== null)
155            needPush = true;
156
157        if (needPush)
158            focusableElements.push(element);
159    }
160
161    //NOTE: remove children of invisible elements
162    return arrayUtils.filter(focusableElements, el => !containsElement(invisibleElements, el));
163}
164
165function flattenFocusableTree (node) {
166    const result = [];
167
168    if (!node.skip && node.el.nodeType !== Node.DOCUMENT_NODE && !isIframeElement(node.el))
169        result.push(node.el);
170
171    for (const prop in node.children) {
172        for (const childNode of node.children[prop])
173            result.push(...flattenFocusableTree(childNode));
174    }
175
176    return result;
177}
178
179
180export function getFocusableElements (doc, sort = false) {
181    const root = buildFocusableTree(doc, sort);
182
183    return flattenFocusableTree(root);
184}
185
186function getInvisibleElements (elements) {
187    const invisibleElements = [];
188
189    for (let i = 0; i < elements.length; i++) {
190        if (getElementStyleProperty(elements[i], 'display') === 'none')
191            invisibleElements.push(elements[i]);
192    }
193
194    return invisibleElements;
195}
196
197export function getTabIndexAttributeIntValue (el) {
198    let tabIndex = nativeMethods.getAttribute.call(el, 'tabindex');
199
200    if (tabIndex !== null) {
201        tabIndex = parseInt(tabIndex, 10);
202        tabIndex = isNaN(tabIndex) ? null : tabIndex;
203    }
204
205    return tabIndex;
206}
207
208export function containsElement (elements, element) {
209    if (elements.contains)
210        return elements.contains(element);
211
212    return arrayUtils.some(elements, parent => parent.contains(element));
213}
214
215export function getTextareaIndentInLine (textarea, position) {
216    const textareaValue = getTextAreaValue(textarea);
217
218    if (!textareaValue)
219        return 0;
220
221    const topPart      = textareaValue.substring(0, position);
222    const linePosition = topPart.lastIndexOf('\n') === -1 ? 0 : topPart.lastIndexOf('\n') + 1;
223
224    return position - linePosition;
225}
226
227export function getTextareaLineNumberByPosition (textarea, position) {
228    const textareaValue = getTextAreaValue(textarea);
229    const lines         = textareaValue.split('\n');
230    let topPartLength   = 0;
231    let line            = 0;
232
233    for (let i = 0; topPartLength <= position; i++) {
234        if (position <= topPartLength + lines[i].length) {
235            line = i;
236
237            break;
238        }
239
240        topPartLength += lines[i].length + 1;
241    }
242
243    return line;
244}
245
246export function getTextareaPositionByLineAndOffset (textarea, line, offset) {
247    const textareaValue = getTextAreaValue(textarea);
248    const lines         = textareaValue.split('\n');
249    let lineIndex       = 0;
250
251    for (let i = 0; i < line; i++)
252        lineIndex += lines[i].length + 1;
253
254    return lineIndex + offset;
255}
256
257// NOTE: the form is also submitted on enter key press if there is only one input of certain
258// types (referred to as types that block implicit submission in the HTML5 standard) on the
259// form and this input is focused (http://www.w3.org/TR/html5/forms.html#implicit-submission)
260export function blocksImplicitSubmission (el) {
261    let inputTypeRegExp = null;
262
263    if (browserUtils.isSafari)
264        inputTypeRegExp = /^(text|password|color|date|time|datetime|datetime-local|email|month|number|search|tel|url|week|image)$/i;
265    else if (browserUtils.isFirefox)
266        inputTypeRegExp = /^(text|password|date|time|datetime|datetime-local|email|month|number|search|tel|url|week|image)$/i;
267    else if (browserUtils.isIE)
268        inputTypeRegExp = /^(text|password|color|date|time|datetime|datetime-local|email|file|month|number|search|tel|url|week|image)$/i;
269    else
270        inputTypeRegExp = /^(text|password|datetime|email|number|search|tel|url|image)$/i;
271
272    return inputTypeRegExp.test(el.type);
273}
274
275export function isEditableElement (el, checkEditingAllowed) {
276    return checkEditingAllowed ?
277        isTextEditableElementAndEditingAllowed(el) || isContentEditableElement(el) :
278        isTextEditableElement(el) || isContentEditableElement(el);
279}
280
281export function isElementContainsNode (parentElement, childNode) {
282    if (isTheSameNode(childNode, parentElement))
283        return true;
284
285    const childNodes = nativeMethods.nodeChildNodesGetter.call(parentElement);
286    const length     = getChildNodesLength(childNodes);
287
288    for (let i = 0; i < length; i++) {
289        const el = childNodes[i];
290
291        if (!isShadowUIElement(el) && isElementContainsNode(el, childNode))
292            return true;
293    }
294
295    return false;
296}
297
298export function isOptionGroupElement (element) {
299    return hammerhead.utils.dom.instanceToString(element) === '[object HTMLOptGroupElement]';
300}
301
302export function getElementIndexInParent (parent, child) {
303    const children = parent.querySelectorAll(getTagName(child));
304
305    return arrayUtils.indexOf(children, child);
306
307}
308
309export function isTheSameNode (node1, node2) {
310    //NOTE: Mozilla has not isSameNode method
311    if (node1 && node2 && node1.isSameNode)
312        return node1.isSameNode(node2);
313
314    return node1 === node2;
315}
316
317export function getElementDescription (el) {
318    const attributes = {
319        id:      'id',
320        name:    'name',
321        'class': 'className',
322    };
323
324    const res = [];
325
326    res.push('<');
327    res.push(getTagName(el));
328
329    for (const attr in attributes) {
330        if (attributes.hasOwnProperty(attr)) {
331            const val = el[attributes[attr]];
332
333            if (val)
334                res.push(' ' + attr + '="' + val + '"');
335        }
336    }
337
338    res.push('>');
339
340    return res.join('');
341}
342
343export function getFocusableParent (el) {
344    const parents = getParents(el);
345
346    for (let i = 0; i < parents.length; i++) {
347        if (isElementFocusable(parents[i]))
348            return parents[i];
349    }
350
351    return null;
352}
353
354export function remove (el) {
355    if (el && el.parentElement)
356        el.parentElement.removeChild(el);
357}
358
359export function isIFrameWindowInDOM (win) {
360    //NOTE: In MS Edge, if an iframe is removed from DOM, the browser throws an exception when accessing window.top
361    //and window.frameElement. Fortunately, setTimeout is set to undefined in this case.
362    if (!win.setTimeout)
363        return false;
364
365    let frameElement = null;
366
367    try {
368        //NOTE: This may raise a cross-domain policy error in some browsers.
369        frameElement = win.frameElement;
370    }
371    catch (e) {
372        return !!win.top;
373    }
374
375    // NOTE: in Firefox and WebKit, frameElement is null for cross-domain iframes even if they are in the DOM.
376    // But these browsers don't execute scripts in removed iframes, so we suppose that the iframe is in the DOM.
377    if ((browserUtils.isFirefox || browserUtils.isWebKit) && win.top !== win && !frameElement)
378        return true;
379
380    return !!(frameElement && nativeMethods.contentDocumentGetter.call(frameElement));
381}
382
383export function isTopWindow (win) {
384    try {
385        //NOTE: MS Edge throws an exception when trying to access window.top from an iframe removed from DOM
386        return win.top === win;
387    }
388    catch (e) {
389        return false;
390    }
391}
392
393export function findIframeByWindow (iframeWindow, iframeDestinationWindow) {
394    const iframes = (iframeDestinationWindow || window).document.getElementsByTagName('iframe');
395
396    for (let i = 0; i < iframes.length; i++) {
397        if (nativeMethods.contentWindowGetter.call(iframes[i]) === iframeWindow)
398            return iframes[i];
399    }
400
401    return null;
402}
403
404export function isEditableFormElement (element) {
405    return isTextEditableElement(element) || isSelectElement(element);
406}
407
408export function getCommonAncestor (element1, element2) {
409    if (isTheSameNode(element1, element2))
410        return element1;
411
412    const el1Parents   = [element1].concat(getParents(element1));
413    let commonAncestor = element2;
414
415    while (commonAncestor) {
416        if (arrayUtils.indexOf(el1Parents, commonAncestor) > -1)
417            return commonAncestor;
418
419        commonAncestor = nativeMethods.nodeParentNodeGetter.call(commonAncestor);
420    }
421
422    return commonAncestor;
423}
424
425export function getChildrenLength (children) {
426    return nativeMethods.htmlCollectionLengthGetter.call(children);
427}
428
429export function getChildNodesLength (childNodes) {
430    return nativeMethods.nodeListLengthGetter.call(childNodes);
431}
432
433export function getInputValue (input) {
434    return nativeMethods.inputValueGetter.call(input);
435}
436
437export function getTextAreaValue (textArea) {
438    return nativeMethods.textAreaValueGetter.call(textArea);
439}
440
441export function setInputValue (input, value) {
442    return nativeMethods.inputValueSetter.call(input, value);
443}
444
445export function setTextAreaValue (textArea, value) {
446    return nativeMethods.textAreaValueSetter.call(textArea, value);
447}
448
449export function getElementValue (element) {
450    if (isInputElement(element))
451        return getInputValue(element);
452    else if (isTextAreaElement(element))
453        return getTextAreaValue(element);
454
455    /*eslint-disable no-restricted-properties*/
456    return element.value;
457    /*eslint-enable no-restricted-properties*/
458}
459
460export function setElementValue (element, value) {
461    if (isInputElement(element))
462        return setInputValue(element, value);
463    else if (isTextAreaElement(element))
464        return setTextAreaValue(element, value);
465
466    /*eslint-disable no-restricted-properties*/
467    element.value = value;
468    /*eslint-enable no-restricted-properties*/
469
470    return value;
471}
472
473export function isShadowElement (element) {
474    return element && element.getRootNode && findDocument(element) !== element.getRootNode();
475}
476
477export function contains (element, target) {
478    if (!element || !target)
479        return false;
480
481    if (element.contains)
482        return element.contains(target);
483
484    return !!findParent(target, true, node => node === element);
485}
486
487export function isNodeEqual (el1, el2) {
488    return el1 === el2;
489}
490
491export function getNodeText (el) {
492    return nativeMethods.nodeTextContentGetter.call(el);
493}
494
495export function getImgMapName (img) {
496    return img.useMap.substring(1);
497}
498
499export function getDocumentElement (win) {
500    return win.document.documentElement;
501}
502
503export function isDocumentElement (el) {
504    return el === document.documentElement;
505}
506
507export function isIframeWindow () {
508    return false;
509}
510
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Testcafe on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)