(el, suppliedPath) => { const norm = s => (s || '').replace(/\s+/g, ' ').trim().slice(0, 512); const tag = el.tagName.toLowerCase(); const type = (el.getAttribute('type') || 'text').toLowerCase(); const auto = (el.getAttribute('autocomplete') || '').toLowerCase(); const sensitive = type === 'password' || /password|cc-|one-time-code/.test(auto); const roles = {button:'button', a:'link', textarea:'textbox', select:'combobox', summary:'button', h1:'heading', h2:'heading', h3:'heading', p:'text', output:'status'}; const inputRoles = {checkbox:'checkbox', radio:'radio', button:'button', submit:'button', range:'slider'}; const role = el.getAttribute('role') || (tag === 'input' ? (inputRoles[type] || 'textbox') : roles[tag]) || 'generic'; const root = el.getRootNode(); const labelled = (el.getAttribute('aria-labelledby') || '').split(/\s+/) .map(id => root.getElementById?.(id)?.textContent || '').join(' ').trim(); const label = [...(el.labels || [])].map(x => x.textContent).join(' '); const name = sensitive ? '[REDACTED]' : norm(labelled || el.getAttribute('aria-label') || label || (['input','textarea','select'].includes(tag) ? (el.getAttribute('placeholder') || el.getAttribute('title')) : el.textContent)); const rect = el.getBoundingClientRect(); const css = getComputedStyle(el); const visible = !!(el.isConnected && rect.width && rect.height && css.visibility !== 'hidden' && css.display !== 'none'); const enabled = !el.matches(':disabled') && el.getAttribute('aria-disabled') !== 'true' && !el.closest('[inert]'); let path = [], node = el; while (suppliedPath == null && node?.nodeType === 1) { const parent = node.parentNode; path.push([...parent.children].indexOf(node)); node = parent.host || parent; } return {role, name, path:suppliedPath ?? path.reverse().join('/'), visible, enabled, sensitive}; }