vyvchy
    Теми розділу

    03 · Веб і мережі для AQA

    DOM, селектори та події

    Зміст

    Кнопку видно на екрані, у DevTools вона підсвічується — а тест каже «element not found». Далі година йде на правку селектора, і найчастіше даремно: селектор коректний. Розходження в іншому: інженер уявляє сторінку як HTML-файл, а браузер працює з іншим обʼєктом — живим деревом у памʼяті, яке скрипти перебудовують безперервно.

    Ця глава — повний виклад теми на сайті: інші глави посилаються сюди по механіку дерева, селекторів і подій. Мета вузька: за хвилину відповісти, з чим саме працює ваш (locator), чому обробник кліку висить не там, де ви думали, і чому елемент, який видно очима, для пошуку не існує.

    Що таке DOM

    DOM (Document Object Model, обʼєктна модель документа) — програмний інтерфейс до документа: він подає сторінку як дерево вузлів і обʼєктів, щоб код міг змінювати її структуру, стилі та вміст. Ключове слово — «подає»: це не текст і не файл, а структура в памʼяті браузера. Розібравши HTML, браузер будує дерево DOM і вже його використовує, щоб показати сторінку.

    Документ подано логічним деревом: кожна гілка завершується вузлом (node), а вузол містить обʼєкти. Вузлом є все, що є всередині документа — елемент лише один із різновидів, поруч живуть текстові вузли й коментарі, а коренем стоїть document. І окремо для тих, хто вивчив DOM «як частину JavaScript»: DOM не є мовою програмування — це вебплатформенний API, спроєктований незалежним від мови. Поведінку дерева й подій нормує DOM Living Standard від WHATWG.

    Вузли повʼязані родинними стосунками: батьківський, дочірні, сусідні, нащадки на будь-якій глибині та предки — на них тримаються комбінатори CSS і осі XPath. Наслідок, який регулярно дає несподівані збіги: пробіли розмітки теж стають вузлами, бо браузери вставляють текстові вузли, щоб подати пробіли вихідного коду. Тому node.childNodes[0] може вказувати на пробільний текст, а не на елемент, а element.childNodes (усі вузли, включно з текстом і коментарями) та element.children (лише елементи) дають різне.

    Вихідний HTML і DOM — два різні документи

    Вихідний HTML (source HTML) — це текст, який сервер віддав у відповідь на запит; саму відповідь розбирає глава «HTTP: методи, структура, заголовки». DOM — це те, що з цього тексту вийшло після парсингу й виконання всього JavaScript. Як обʼєктне представлення сторінки DOM змінюється скриптами вже після завантаження, тож два документи розходяться тим сильніше, чим більше на сторінці клієнтської логіки.

    У SPA (single-page application) розходження максимальне за означенням: такий застосунок завантажує лише один вебдокумент, а далі змінює вміст його тіла через JavaScript-API на кшталт fetch(). Тобто у відповіді сервера інтерфейсу може не бути взагалі — лише каркас, а таблиці, кнопки й тексти зʼявляються в дереві після виконання скрипта й запитів до API.

    СерверБраузерСерверБраузерПарсинг — у DOM ще немає інтерфейсуСкрипт наповнює дерево — тепер локатор бачить елементиGET /dashboardHTML-каркас і посилання на скриптЗапит до API за данимиJSONСерверБраузерСерверБраузерПарсинг — у DOM ще немає інтерфейсуСкрипт наповнює дерево — тепер локатор бачить елементиGET /dashboardHTML-каркас і посилання на скриптЗапит до API за данимиJSON

    Звідси головна теза глави: локатор шукає елемент не в HTML-коді, а в поточному стані DOM.

    Наша практика (не канон). Джерела дають механізм, але не діагностичний прийом — далі те, як це роблять команди, з якими ми працювали. Порівнюйте два вікна: «View Page Source» (те саме видно в тілі відповіді на вкладці Network) проти панелі Elements у DevTools. Перше — текст, який віддав сервер; друге — поточне дерево після виконання скриптів. Елемент є в Elements і немає у View Source — сторінка динамічна, і жоден інструмент без виконання JavaScript (HTTP-клієнт, curl, HTML-парсер) її вмісту не побачить: для UI-тестів потрібен справжній браузер. Типовий каркас SPA у вихідному коді — це порожній <div id="root"> плюс <script>. Дзеркальний висновок економить години: якщо потрібні не кнопки, а дані, дешевше піти напряму до API, який це дерево наповнює.

    Дерево живе: динамічність і stale element

    JavaScript додає, видаляє, переміщує й переписує вузли у відповідь на дії користувача, відповіді сервера й таймери: модального вікна не було — воно зʼявилося на кліку, спінер зник після відповіді API, тост прожив дві секунди. Дерево не «збирається один раз».

    Правильна відповідь на цю рухомість — не пауза на три секунди, а очікування конкретної умови: щоб елемент став видимим, клікабельним або зник. Механіку очікувань розбирає глава «Практичні сценарії AQA: флак і синхронізація», а момент, коли дерево SPA можна вважати готовим, — глава «Асинхронне завантаження: SPA/MPA, CSR/SSR».

    Друге, що випливає з рухомості дерева, — модель самого локатора. Локатор Playwright — не посилання на вузол, а «рецепт пошуку», який резолвиться в реальний елемент у момент дії. Це фундаментальна відмінність від моделі Selenium WebElement, і саме через неї класична проблема застарілого посилання там майже не виникає.

    // знімок вузла: після ререндеру посилання вказує в порожнечу
    const row = await page.$('.row');
    await sortTable();               // фреймворк перемалював таблицю
    await row.click();               // цього вузла в дереві вже немає
    
    // локатор: опис пошуку, який виконається в момент дії
    const rowLocator = page.locator('.row');
    await sortTable();
    await rowLocator.click();        // пошук відбудеться зараз, у свіжому дереві

    Наша практика (не канон). Під наступні два твердження канонічного джерела в реєстрі немає — це наш висновок із того, як улаштоване дерево. Перше: динамічність DOM — головна причина нестабільності UI-тестів, бо тест і сторінка виконуються паралельно; спроба клікнути в ту мілісекунду, коли фреймворк ще не домалював елемент, дає «не знайдено», а через мить елемент уже на місці. Друге: stale element — це збережене посилання на вузол, якого вже немає в дереві або який замінили новим після ререндеру; лікується воно не навмання, а тим, щоб не тримати посилання надовго й шукати елемент безпосередньо перед дією.

    Селектор і локатор: одна механіка, різна мета

    Селектор — шаблон, за яким добирають елементи; ті самі селектори працюють і в JavaScript, щоб діставати вузли з DOM. Локатор в автотесті будується поверх тієї самої механіки: він описує, як знайти елемент у поточному стані дерева. Тому «CSS проти локатора фреймворку» — не два світи, а надбудова над одним.

    Модуль CSS selectors описує понад 60 селекторів і пʼять комбінаторів, а також їхню специфічність. Для локатора специфічність вторинна: тут важить не перемога в каскаді, а унікальність і стабільність збігу — локатор має пережити наступний реліз фронтенду. Добирати елементи можна за типом (тегом), класом, ідентифікатором, атрибутом, універсально, а також за поточним станом і позицією в дереві.

    КомбінаторЗначення
    A B (пробіл)B десь усередині A, на будь-якій глибині
    A > BB — безпосередня дитина A
    A + BB — наступний сусід одразу після A
    A ~ BB — будь-який подальший сусід A

    Пʼятий, комбінатор стовпця ||, у специфікації описаний, але браузери його не підтримують; кома ж комбінатором не є — це список селекторів. Часта помилка новачка саме тут: сплутати пробіл і >. div span знайде span на будь-якій глибині, div > span — лише прямих дітей; у глибокій верстці це різниця між одним потрібним елементом і десятком зайвих.

    Селектори за атрибутами вміють часткове зіставлення: ^= — «починається з», $= — «закінчується на», *= — «містить щонайменше одне входження підрядка». Саме воно рятує, коли фреймворк генерує класи з випадковим хешем на кшталт Button_root__a1b2c: чіплятися за повний клас безглуздо, а [class^="Button_root"] переживе перезбірку. Поруч пастка: ~= до цієї трійки не належить — він шукає рівно таке слово в списку через пробіл, тож [class~="logo"] знайде class="header logo", але не class="logotype".

    Псевдокласи добирають елементи за станом, якого немає в дереві документа: наведення, фокус, вимкненість, відвіданість. У тестах це зручно — input:checked знаходить елемент лише коли він відмічений. Позиційні (:nth-child) крихкі: вставили рядок вище — і селектор указує вже на інше. Окремо варто знати реляційний :has(): він добирає предка або попереднього сусіда відносно опорного елемента (tr:has(input:checked) — рядок із відміченим чекбоксом). За статусом Baseline він «Widely available» в усіх браузерах із грудня 2023 року, тож на старіших збірках браузерів підтримку перевіряють окремо.

    Коли справді потрібен XPath

    Дві речі XPath уміє, а набір комбінаторів CSS — ні. Перша: рух угору по дереву. Вісь parent дає батька контекстного вузла, ancestor — усіх його предків, preceding-sibling веде до попередніх сусідів, а скорочення .. — це рівно parent::node(). Частину випадків тепер закриває :has(), але лише на збірках браузерів, новіших за грудень 2023. Друга — зіставлення за текстом: вузловий тест text() істинний для будь-якого текстового вузла, а рядкове значення елемента є конкатенацією рядкових значень усіх його текстових нащадків у порядку документа. Платня за обидві можливості — багатослівність і вища крихкість виразу.

    Класична пастка XPath 1.0 виростає з трьох нешкідливих поодинці правил: text() повертає множину вузлів, а не рядок; аргумент функції, що чекає рядок, конвертується так, ніби викликали функцію string; а конвертація множини вузлів бере значення вузла, першого в порядку документа. Разом це означає, що для <button>Оплата <b>картою</b></button> вираз contains(text(), 'Оплата картою') не спрацює — перевіряється лише перший текстовий вузол. Ліки — крапка замість text(): contains(., 'Оплата картою') бачить напис цілком. Друга пастка того самого класу — індексація: //x[1] означає не «перший x у документі», а «усі x, що є першими дітьми своїх батьків»; справді перший збіг беруть як (//x)[1].

    Що робить локатор стабільним

    Найстабільніший спосіб знайти елемент — той, яким його знаходить сам користувач і читач екрана: за роллю та видимим текстом. Роль відповідає семантиці елемента в дереві доступності, а доступне імʼя формується з видимого тексту, aria-label чи повʼязаного label. Testing Library формулює принцип прямо: «The more your tests resemble the way your software is used, the more confidence they can give you». Доступність як предмет — глава «Семантичний HTML і доступність»; конкретні API локаторів — розділ про інструменти.

    Цінність тут у незалежності від верстки: розробник переверстав й перейменував класи — роль і напис лишились, локатор живий. Побічно такі локатори ловлять і дефекти доступності: якщо кнопка насправді клікабельний <div> без ролі, пошук за роллю її не знайде. Коли ні роль, ні текст не дають надійного якоря (іконка без підпису, напис, що змінюється), беруть data-testid — атрибут даних, який існує заради тестів і робить контракт між тестом і кодом явним.

    Наша практика (не канон). Порядок переваги джерела не задають — це наше ранжування: роль плюс доступне імʼя → текст або лейбл → data-testid → CSS за стабільним атрибутом → XPath. Автогенеровані класи й ланцюжки на кшталт .btn-primary.mt-3 > span як основний підхід тримаємо поза списком. Дві ціни варто сказати вголос. Локатори за текстом крихкі до локалі: привʼязка до українського напису зламається на англійській збірці, тому тестують у фіксованій локалі або йдуть через окремий атрибут. А data-testid вимагає командної домовленості й легко зникає при рефакторингу — для розробника, який не пише тестів, цей атрибут «нічого не робить».

    Події: як клік знаходить обробника

    Взаємодія користувача зі сторінкою — це потік подій: браузер генерує подію, а код реагує на неї через обробники. Подія, надіслана елементу в дереві, може дістатися й обробників на його предках — саме звідси ростуть делегування подій і половина «загадкових» кліків.

    Подія проходить дерево трьома фазами: захоплення (capturing) — від кореня до цілі, фаза цілі, спливання (bubbling) — назад угору; поточну фазу видно у властивості eventPhase. Спершу в порядку дерева спрацьовують слухачі з capture: true, потім, якщо подія спливає, — у зворотному порядку решта. За замовчуванням обробники стоять на спливанні; щоб слухати на захопленні, третім аргументом addEventListener передають true.

    1 · захоплення

    3 · спливання

    document

    контейнер

    картка

    кнопка — ціль, фаза 2

    картка

    контейнер

    document

    1 · захоплення

    3 · спливання

    document

    контейнер

    картка

    кнопка — ціль, фаза 2

    картка

    контейнер

    document

    Три методи пояснюють більшість «чому не спрацювало». stopPropagation() не дає події дістатися інших обʼєктів дерева, але стандартної дії браузера не скасовує — звідси загадки, коли клік по вкладеному елементу не доходить до зовнішнього обробника; stopImmediatePropagation() додатково відсікає решту слухачів на тому самому обʼєкті; preventDefault() скасовує стандартну дію лише для подій із cancelable: true і поширення не спиняє, а успіх видно через defaultPrevented. Практичний наслідок: якщо клік по «Надіслати» не веде до навігації, це може бути не баг, а свідомий preventDefault() в обробнику submit — перевіряти тоді треба не URL, а тост чи запит у Network.

    ПодіяКоли виникає
    clickнатискання на елементі
    inputбудь-яка зміна значення поля
    changeзміна значення (чим відрізняється від input — далі в підрозділі)
    submitвідправлення форми
    focus / blurотримання / втрата фокуса
    keydown / keyupнатискання / відпускання клавіші

    Специфікацій тут дві, і це не бухгалтерія. focus, blur, focusin, focusout, keydown і keyup нормує W3C UI Events; click, input, change і submit живуть у переліку подій HTML Standard, і в UI Events їх немає взагалі. Різниця не в назві документа, а в інтерфейсі: change іде з інтерфейсом Event і цілями «Form controls», submit — з інтерфейсом SubmitEvent і ціллю «form elements», click — з PointerEvent. Інтерфейс визначає, які поля в події є, тож шукати submit серед подій інтерфейсу користувача — марна витрата часу. Звідти ж і точна межа пари, яку плутають найчастіше: input спрацьовує, «when the user changes the contenteditable element's content, or the form control's value», а change — коли користувач значення зафіксував («commits a value change»). І пояснення, чому Enter на кнопці дає клік: click стандарт описує як подію, що спрацьовує синтетично й тоді, коли елемент активують непозиційним пристроєм — «e.g. a keyboard».

    Спливають не всі події однаково, і це визначає, куди можна повісити один обробник на групу полів. blur не спливає — UI Events позначає його «Bubbles: No» і описує як подібний до focusout, але без спливання; focusin і focusout спливають обидва, причому focus нормативно спрацьовує перед focusin, а blur — перед focusout.

    Делегування (event delegation) прямо випливає зі спливання: один обробник на спільному предку обслуговує всі дочірні елементи, зокрема ті, що зʼявляться згодом. Важлива деталь про ціль: e.target — найглибший елемент, по якому реально клікнули. Якщо кнопка містить <svg>, ціллю буде саме <svg> — тому в обробнику беруть e.target.closest('.delete-btn').

    Наша практика (не канон). Далі — наслідки, під які канонічного джерела в реєстрі немає. Фреймворки зазвичай слухають саме input, тому пряме присвоєння element.value = '...' без генерації події застосунок не помітить: стан у React чи Vue не оновиться. Через це штатні дії тестових фреймворків (fill, type) не пишуть значення напряму, а емулюють справжні події вводу — і саме тому не варто смикати DOM через evaluate. Ще одна деталь до пари input/change, якої стандарт не формулює: для текстового поля «зафіксував значення» на практиці означає втрату фокуса — тоді й приходить change, тоді як input приходить на кожен символ. І два висновки про перевірки: обробник може фізично не висіти на елементі, по якому ви клікаєте, тож питання «чи є onclick на кнопці» безглузде — перевіряють результат кліку; а делегування пояснює, чому щойно доданий динамічний елемент одразу «працює» без перевішування обробників, тож клік по ньому має спрацьовувати, щойно він зʼявився й став видимим.

    Межі пошуку: Shadow DOM та iframe

    Shadow DOM дає елементу власне приховане під-дерево, ізольоване від основного документа. Термінологія фіксована: shadow host — елемент-носій, — внутрішнє дерево, — його корінь, — межа між shadow DOM і звичайним DOM. Shadow tree ніколи не існує саме по собі — воно прикріплене до іншого дерева через свій host, а створює його виклик attachShadow(). Ізоляція двобічна: CSS сторінки не діє на вузли компонента, а його стилі не протікають назовні.

    Головне для тестів: document.querySelectorAll() не бачить вузлів усередині shadow tree — вони фактично приховані від JavaScript сторінки. Селектор, написаний для основного документа, shadow boundary не пробиває: треба дістатись хоста, потім його shadowRoot, і шукати вже там. Режим open віддає корінь через element.shadowRoot, closed повертає null — але це сигнал наміру, а не барʼєр безпеки, і MDN каже прямо: «you should not consider this a strong security mechanism, because there are ways it can be evaded, for example by browser extensions running in the page». Тому висновок «із closed shadow DOM усі інструменти автоматизації безсилі» джерелом не підтверджений: драйвери працюють на рівні браузера — рівно там, звідки режим і обходиться. Практична асиметрія теж задокументована: Playwright автоматично пробиває відкриті shadow tree своїми CSS- і текстовими локаторами, а два винятки названо прямо — XPath межу shadow tree не перетинає, і закриті shadow root не підтримуються.

    Межа працює і для подій: коли подія перетинає shadow boundary, спрацьовує ретаргетинг — ціль підміняється на host-елемент, тож зовні видно компонент, а не його внутрішні вузли. Той самий принцип у composedPath(): він повертає шлях події, приховуючи вузли закритих shadow tree.

    iframe робить схоже, але радикальніше: він вбудовує окремий самостійний документ зі своїм window і своїм деревом. Це не «ще один шар» сторінки, тому пошук по DOM головного документа елементів усередині фрейма не бачить — контекст спершу перемикають на потрібний фрейм (як саме — канон розділу про інструменти), а в Selenium після роботи з фреймом обовʼязково повертаються в основний контекст.

    До межі дерева додається межа безпеки. (same-origin policy) обмежує, як документ або скрипт одного взаємодіє з ресурсом іншого; походження — це трійка «схема / хост / порт» (докладно — глава «HTTPS, TLS і безпека» і глава про CORS). Вбудувати чужу сторінку через iframe можна, а прочитати її вміст — ні: посилання на вікно чужого походження дає дуже обмежений доступ, штатним каналом лишається window.postMessage. Драйвери автоматизації працюють поза цим обмеженням, на рівні браузера, тому в фрейм перемкнутися вміють; деталі залежать від браузера, драйвера та версій. Звідси й типова розгубленість AQA: «у DevTools елемент видно, а локатор не знаходить» — часто не про селектор, а про контекст.

    Наша практика (не канон). Дві деталі без канонічного джерела. У власних елементах (web components) тег нестандартний — <my-button> замість <button>, — а роль доводиться виставляти вручну через ARIA, тому орієнтуються на роль і доступне імʼя самого компонента, а домовленість про data-testid економить багато болю. І адреса, за якою шукати iframe: платіжні форми, віджети підтримки, вбудовані відео й рекламні блоки — саме там ламаються «звичайні» локатори.

    Типові помилки

    • Виглядає як «елемента немає на сторінці», а насправді його немає у вихідному HTML, але він є в DOM: інструмент без виконання JavaScript бачить лише текст відповіді сервера.
    • Виглядає як «поганий селектор», а насправді гонка з рендером: стабільне падіння вказує на селектор, недетерміноване — на таймінг.
    • Виглядає як «елемент зник посеред тесту», а насправді його замінили новим при ререндері, і збережене посилання вказує в порожнечу — сам локатор коректний.
    • Виглядає як «кнопка без onclick, отже мертва», а насправді обробник висить на предку через делегування — перевіряти треба результат кліку, а не наявність атрибута.
    • Виглядає як «текст на кнопці є, а XPath не знаходить», а насправді contains(text(), ...) бере лише перший текстовий вузол; напис цілком бачить contains(., ...).
    • Виглядає як «локатор зламався», а насправді елемент живе в іншому дереві — у shadow tree компонента або в документі iframe.

    Підсумок

    1. DOM — не текст і не файл, а дерево вузлів і обʼєктів у памʼяті браузера; локатор працює з його поточним станом, а не з HTML, який віддав сервер.
    2. У SPA вихідний HTML може не містити інтерфейсу взагалі — для UI потрібен справжній браузер, а за самими даними дешевше піти в API.
    3. Дерево змінюється безперервно, тож посилання на вузол швидко протухає: елемент шукають перед дією, а на асинхронність відповідають очікуванням умови, не паузою.
    4. Селектор і локатор — одна механіка; для локатора важить не специфічність, а унікальність і стійкість збігу до наступного релізу фронтенду.
    5. Подія доступна обробникам на предках, а Shadow DOM та iframe ставлять межу, крізь яку звичайний пошук не йде: «видно в DevTools, а тест не знаходить» — питання контексту.

    Можливі питання

    • «Чим DOM відрізняється від HTML-коду сторінки?» Перевіряють базу. Сильна відповідь називає різницю природи (текст відповіді проти дерева обʼєктів у памʼяті) і додає наслідок: локатор шукає в DOM.
    • «Чому curl не бачить вміст сторінки, яку ви щойно дивилися в браузері?» Дивляться, чи розумієте, що інтерфейс SPA домальовує JavaScript на клієнті, а не приходить у тілі відповіді.
    • «Чим div span відрізняється від div > span Класика на комбінатори; сильний кандидат додає, чому в глибокій верстці це різниця між одним елементом і десятком зайвих.
    • «Що таке stale element і як з ним боротися?» Слухають механізм (вузол замінили при ререндері), а не назву винятку, і лікування: не тримати посилання, шукати перед дією.
    • «Розкажіть про фази проходження події.» Очікують три фази, дефолт на спливанні й розуміння, що stopPropagation() і preventDefault() роблять різні речі.
    • «Елемент видно в DevTools, а локатор його не знаходить. Ваші дії?» Міні-кейс, у якому цінують порядок: спершу контекст (iframe, Shadow DOM), потім момент часу, і лише тоді сам селектор.

    Джерела

    Що таке DOM

    • MDN — Introduction to the DOM — DOM як програмний інтерфейс до документа, дерево вузлів і обʼєктів, document як корінь і статус DOM як вебплатформенного API.
    • WHATWG DOM Standard — нормативна модель дерева й родинні відношення вузлів.
    • MDN — Node: childNodes property — пробільні текстові вузли, різниця childNodes і children, живість колекції.

    Вихідний HTML і DOM — два різні документи

    Дерево живе: динамічність і stale element

    Селектор і локатор: одна механіка, різна мета

    • MDN — CSS selectors — селектор як шаблон добору, ті самі селектори в JavaScript, базові типи, пʼять комбінаторів, специфічність і псевдокласи.
    • MDN — Attribute selectors — часткове зіставлення ^=, $=, *=, окрема семантика ~= і прапорці i/s.
    • MDN — :has() CSS pseudo-class — добір предка за нащадком і статус Baseline із грудня 2023 року.
    • Playwright — Locators — локатор як опис пошуку елемента, для якого важать унікальність і стійкість збігу.

    Коли справді потрібен XPath

    • W3C — XML Path Language (XPath) Version 1.0 — осі parent, ancestor, preceding-sibling і скорочення .., вузловий тест text(), рядкове значення елемента, конвертація множини вузлів у рядок і застереження про //para[1].

    Що робить локатор стабільним

    • Playwright — Locators — пошук за роллю й доступним імʼям як рекомендований підхід і незалежність такого локатора від верстки.
    • Testing Library — Guiding Principles — принцип «що ближче тест до способу використання, то більше довіри».
    • MDN — Use data attributes — атрибути даних як власні дані на елементі.

    Події: як клік знаходить обробника

    • WHATWG DOM Standard — потік подій і доступність обробників на предках, три фази й eventPhase, порядок виклику слухачів, stopPropagation(), stopImmediatePropagation(), preventDefault() і defaultPrevented, делегування та e.target як найглибша ціль.
    • W3C — UI Events — події фокуса й клавіатури: «Bubbles: No» для blur, спливання focusin/focusout і нормативний порядок між ними.
    • WHATWG HTML Standard — Index: List of eventsclick, input, change і submit із їхніми інтерфейсами (PointerEvent, Event, SubmitEvent), межа inputchange («commits a value change») і синтетичний click від непозиційного пристрою.
    • MDN — Introduction to events — скасування стандартної дії форми обробником submit.

    Межі пошуку: Shadow DOM та iframe

    • MDN — Using shadow DOM — терміни shadow host/tree/root/boundary, attachShadow(), двобічна ізоляція стилів, невидимість вузлів shadow tree для querySelectorAll(), режими open/closed і застереження про closed.
    • WHATWG DOM Standard — прикріпленість shadow tree до host, режими кореня, ретаргетинг події на межі та composedPath().
    • MDN — Same-origin policy — походження як трійка «схема / хост / порт», обмежений доступ до вікна чужого походження і window.postMessage.
    • Playwright — Frames — фрейм як окремий документ, у який контекст пошуку треба перемкнути.
    • Selenium — Working with IFrames and frames — повернення в основний контекст після роботи з фреймом.
    • Playwright — Locators — симптом «видно вручну, а тест не знаходить» як питання контексту пошуку; CSS- і текстові локатори пробивають відкриті shadow tree, а винятки — XPath і закриті shadow root.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.