Я займаюся фронтенд розробкою досить довго, щоб помітити тенденцію за ці роки: молодші розробники працюють із новою парадигмою програмування, не розуміючи її історичного контексту. Звісно, цілком зрозуміло щось не знати. Інтернет — це дуже велике місце з різноманітним набором навичок і спеціальностей, і ми не завжди знаємо, чого не знаємо. Навчання в цій галузі – це безперервна подорож, а не те, що відбувається один раз і закінчується. Приклад: хтось із моєї команди запитав, чи можна визначити, чи користувачі переходять із певної вкладки в інтерфейсі користувача. Я вказав на подію beforeunload JavaScript. Але ті, хто стикався з цим раніше, знають, що це можливо, оскільки вони отримували сповіщення про незбережені дані на інших сайтах, для яких beforeunload є типовим випадком використання. Я також вказав моєму колезі на події pageHide і visibilityChange. Звідки я про це дізнався? Тому що це з’явилося в іншому проекті, а не тому, що я вивчав це, коли спочатку вивчав JavaScript. Справа в тому, що сучасні фронтенд-фреймворки стоять на плечах технологічних гігантів, які їм передували. Вони абстрагують практики розробки, часто для кращого досвіду розробника, який зменшує або навіть усуває потребу знати або торкатися того, що традиційно є основними інтерфейсними концепціями, які, ймовірно, повинен знати кожен. Розглянемо об’єктну модель CSS (CSSOM). Можна очікувати, що кожен, хто працює з CSS і JavaScript, має великий практичний досвід CSSOM, але це не завжди так. Був проект React для сайту електронної комерції, над яким я працював, де нам потрібно було завантажити таблицю стилів для поточного вибраного постачальника платежів. Проблема полягала в тому, що таблиця стилів завантажувалася на кожній сторінці, коли вона була дійсно потрібна лише на певній сторінці. Розробник, якому доручено це зробити, ніколи не завантажував таблицю стилів динамічно. Знову ж таки, це цілком зрозуміло, коли React абстрагується від традиційного підходу, до якого ви могли б прагнути. CSSOM, ймовірно, не те, що вам потрібно у вашій повсякденній роботі. Але цілком імовірно, що вам знадобиться взаємодіяти з ним у певний момент, навіть у одноразовому випадку. Цей досвід надихнув мене написати цю статтю. Існує багато існуючих веб-функцій і технологій, до яких ви можете ніколи не торкатися безпосередньо у своїй повсякденній роботі. Можливо, ви досить новачок у веб-розробці й просто не знаєте про них, оскільки ви заглиблені в абстракцію конкретного фреймворку, який не вимагає від вас глибоких знань або навіть зовсім. Я говорю саме про XML, який, як відомо багатьом із нас, є стародавньою мовою, не зовсім схожою на HTML. Я згадую це через нещодавні дискусії WHATWG, які припускають, що значну частину стеку XML, відомого як програмування XSLT, слід видалити з браузерів. Це саме та старіша існуюча технологія, яку ми мали роками, і її можна було б використати для чогось такого ж практичного, як ситуація CSSOM, у якій опинилася моя команда. Чи працювали ви раніше з XSLT? Давайте подивимося, чи будемо ми спиратися на цю стару технологію та використовувати її функції поза контекстом XML для вирішення реальних проблем сьогодні. XPath: Центральний API Найважливішою технологією XML, яка є, мабуть, найкориснішою за межами прямої точки зору XML, є XPath, мова запитів, яка дозволяє вам знаходити будь-який вузол або атрибут у дереві розмітки з одним кореневим елементом. Я маю особисту прихильність до XSLT, але це також покладається на XPath, і особисту прихильність слід відкинути в сторону, щоб оцінити важливість. Аргумент для видалення XSLT не згадує XPath, тому я вважаю, що це все ще дозволено. Це добре, тому що XPath є центральним і найважливішим API у цьому наборі технологій, особливо коли ви намагаєтесь знайти щось для використання поза звичайним використанням XML. Це важливо, тому що, хоча селектори CSS можна використовувати для пошуку більшості елементів на вашій сторінці, вони не можуть знайти їх усі. Крім того, селектори CSS не можна використовувати для пошуку елемента на основі його поточної позиції в DOM. XPath може. Деякі з вас, хто читає це, можуть знати XPath, а деякі — ні. XPath — це досить велика сфера технологій, і я не можу навчити всім основам, а також показати вам цікаві речі, які можна з нею робити, в одній подібній статті. Я справді намагався написати цю статтю, але середня публікація в журналі Smashing Magazine не перевищує 5000 слів. Я вже був на більш ніж2000 слів, але лише на півдорозі до основ. Отже, я збираюся робити цікаві речі з XPath і дам вам кілька посилань, які ви можете використовувати для основ, якщо ви вважаєте це цікавим. Поєднання XPath і CSS XPath може робити багато речей, які не можуть селектори CSS під час запиту елементів. Але селектори CSS також можуть робити кілька речей, які XPath не може, а саме запитувати елементи за назвою класу.
CSS XPath .myClass /*[contains(@class, "myClass")]
У цьому прикладі CSS запитує елементи, які містять назву класу .myClass. Тим часом приклад XPath запитує елементи, які містять клас атрибутів із рядком «myClass». Іншими словами, він вибирає елементи з myClass у будь-якому атрибуті, включаючи елементи з назвою класу .myClass — а також елементи з «myClass» у рядку, наприклад .myClass2. XPath є ширшим у цьому сенсі. Отже, ні. Я не пропоную, що ми повинні викинути CSS і почати вибирати всі елементи через XPath. Справа не в цьому. Справа в тому, що XPath може робити речі, які CSS не може, і може бути дуже корисним, навіть якщо це застаріла технологія в стеку браузера і може здаватися неочевидною на перший погляд. Давайте використовувати обидві технології разом не лише тому, що ми можемо, але й тому, що ми дізнаємося дещо про XPath у процесі, зробивши його ще одним інструментом у вашому стеку — про який ви, можливо, не знали, існував весь час! Проблема полягає в тому, що метод document.evaluate JavaScript і різні методи селектора запитів, які ми використовуємо з CSS API для JavaScript, несумісні. Я створив сумісний API для запитів, щоб ми могли розпочати роботу, хоча, правда, я не дуже над цим думав, оскільки це відхилення від того, що ми тут робимо. Ось досить простий робочий приклад багаторазового конструктора запиту: Перегляньте Pen queryXPath [forked] Браяна Расмуссена. Я додав два методи до об’єкта документа: queryCSSSelectors (який по суті є querySelectorAll) і queryXPaths. Обидва вони повертають об’єкт queryResults:
{ queryType: вузли | рядок | номер | логічне значення, результати: будь-який[] // елементи html, елементи xml, рядки, числа, логічні значення, queryCSSSelectors: (запит: рядок, змінити: логічний) => queryResults, queryXpaths: (запит: рядок, змінити: логічний) => queryResults }
Функції queryCSSSelectors і queryXpaths запускають запит, який ви їм надаєте, над елементами в масиві результатів, звичайно, якщо масив результатів має тип вузлів. В іншому випадку він поверне queryResult з порожнім масивом і типом вузлів. Якщо для властивості amend встановлено значення true, функції змінюватимуть власні queryResults. За жодних обставин це не можна використовувати у виробничому середовищі. Я роблю це таким чином виключно для того, щоб продемонструвати різні ефекти спільного використання двох API запитів. Приклад запитів Я хочу показати кілька прикладів різних запитів XPath, які демонструють деякі потужні речі, які вони можуть робити, і те, як їх можна використовувати замість інших підходів. Перший приклад //li/text(). Це запитує всі елементи li та повертає їхні текстові вузли. Отже, якби ми зробили запит до наступного HTML:
- один
- два
- три
…ось що повертається:
{"queryType":"xpathEvaluate","results":["one","two","three"],"resultType":"string"}
Іншими словами, ми отримуємо такий масив: ["один","два","три"]. Зазвичай ви запитуєте елементи li, щоб отримати це, перетворюєте результат цього запиту на масив, відображаєте масив і повертаєте текстовий вузол кожного елемента. Але ми можемо зробити це стисло за допомогою XPath: document.queryXPaths("//li/text()").результати.
Зауважте, що текстовий вузол можна отримати за допомогою text(), який виглядає як підпис функції — і це так. Він повертає текстовий вузол елемента. У нашому прикладі в розмітці є три елементи li, кожен з яких містить текст ("one", "two" і "three").
Давайте розглянемо ще один приклад запиту text(). Припустимо, що це наша розмітка:
Давайте напишемо запит, який повертає значення атрибута href: document.queryXPaths("//a[text() = 'Увійти']/@href").results.
Це запит XPath до поточного документа, як і в попередньому прикладі, але цього разу ми повертаємо атрибут href посилання (елемента), який містить текст «Увійти». Фактичне повернуторезультат ["/login.html"]. Огляд функцій XPath Існує кілька функцій XPath, і ви, ймовірно, не знайомі з ними. Я вважаю, що є кілька, про які варто знати, зокрема такі:
starts-withЯкщо текст починається з конкретного іншого прикладу тексту, starts-with(@href, 'http:') повертає true, якщо атрибут href починається з http:. containsЯкщо текст містить певний інший приклад тексту, contains(text(), "Smashing Magazine") повертає істину, якщо текстовий вузол містить слова "Smashing Magazine" у будь-якому місці. countПовертає кількість збігів із запитом. Наприклад, count(//*[starts-with(@href, 'http:']) повертає кількість посилань у вузлі контексту, які містять елементи з атрибутом href, який містить текст, що починається з http:. substringПрацює як підрядок JavaScript, за винятком того, що ви передаєте рядок як аргумент. Наприклад, substring("my text", 2, 4) повертає "y t". substring-before Повертає частину рядка перед іншим рядком. Наприклад, substing-before("my text", " ") повертає "my". Так само substring-before("hi","bye") повертає порожній рядок. substring-after Повертає частину рядка після іншого рядка. Наприклад, substing-after("мій текст", " ") повертає "текст". Так само substring-after("hi","bye")повертає порожній рядок. normalize-space Повертає рядок аргументу з пробілами, нормалізованими видаленням пробілів на початку та в кінці та заміною послідовностей пробілів на один пробіл. notПовертає логічне значення true, якщо аргумент false, інакше false. true Повертає логічне значення true. false Повертає логічне значення false. concat Те саме, що і JavaScript concat, за винятком того, що ви не запускаєте його як метод у рядку. Натомість ви вставляєте всі рядки, які хочете об’єднати. string-lengthЦе не те саме, що JavaScript string-length, а швидше повертає довжину рядка, наданого як аргумент. translateThis приймає рядок і змінює другий аргумент на третій. Наприклад, translate("abcdef", "abc", "XYZ") виводить XYZdef.
Крім цих конкретних функцій XPath, існує низка інших функцій, які працюють так само, як їхні аналоги JavaScript — або аналоги в основному будь-якою мовою програмування — які, ймовірно, також знайдуться вам у нагоді, як-от підлога, стеля, округлення, сума тощо. Наступна демонстрація ілюструє кожну з цих функцій: Див. Числові функції Pen XPath [forked] Браяна Расмуссена. Зауважте, що, як і більшість функцій маніпулювання рядками, багато числових приймають один вхід. Це, звичайно, тому, що вони повинні використовуватися для запитів, як в останньому прикладі XPath: //li[floor(text()) > 250]/@val
Якщо ви використовуєте їх, як це робиться в більшості прикладів, ви закінчите запускати його на першому вузлі, який відповідає шляху. Є також деякі функції перетворення типів, яких, ймовірно, слід уникати, тому що JavaScript вже має власні проблеми з перетворенням типів. Але іноді вам захочеться перетворити рядок на число, щоб порівняти його з іншим числом. Функції, які встановлюють тип чогось, це логічні значення, числа, рядки та вузли. Це важливі типи даних XPath. І, як ви можете собі уявити, більшість із цих функцій можна використовувати для типів даних, які не є вузлами DOM. Наприклад, substring-after приймає рядок, як ми вже розглянули, але це може бути рядок з атрибута href. Це також може бути просто рядок:
const testSubstringAfter = document.queryXPaths("substring-after('hello world',' ')");
Очевидно, що цей приклад поверне нам масив результатів як ["світ"]. Щоб показати це в дії, я створив демонстраційну сторінку, використовуючи функції проти речей, які не є вузлами DOM: Перегляньте Pen queryXPath [forked] Браяна Расмуссена. Ви повинні звернути увагу на дивовижний аспект функції перекладу, який полягає в тому, що якщо у вас є символ у другому аргументі (тобто, список символів, які ви хочете перекласти), і немає відповідного символу для перекладу, цей символ буде видалено з виводу. Таким чином, це:
translate('Привіт, мене звати Ініго Монтойя, ти вбив мого батька, готуйся померти','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','*')
…результати в рядку, включаючи пробіли: [" * * ** "]
Це означає, що буква «a» перекладається на зірочку (*), але всі інші символи, які не мають перекладу в цільовому рядку, повністю видаляються. Пробіл — це все, що нам залишилосяміж перекладеними символами «a». Потім знову цей запит:
translate('Hello, My Name is Inigo Montoya, you killed my father, prepare to die','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','**************************************************')")
… не має проблеми та видає результат, який виглядає так:
"***** ** **** ** ***** ******* *** ****** ** ****** ******* ** ***"
Вам може здатися, що в JavaScript немає простого способу робити саме те, що робить функція перекладу XPath, хоча для багатьох випадків використання replaceAll з регулярними виразами може впоратися з цим. Ви можете використати той самий підхід, який я продемонстрував, але це неоптимально, якщо все, що вам потрібно, це перекладати рядки. Наступна демонстрація містить функцію перекладу XPath для надання версії JavaScript: Перегляньте функцію перекладу Pen [forked] Браяна Расмуссена. Де можна використати щось подібне? Розглянемо шифрування Caesar Cipher із тризначним зміщенням (наприклад, шифрування у верхній частині рядка з 48 р. до н. е.):
translate("Цезар планує перетнути Рубікон!", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", "XYZABCDEFGHIJKLMNOPQRSTUVWxyzabcdefghijklmnopqrstuvw")
Вхідний текст «Цезар планує перетнути Рубікон!» призводить до "Zxbpxo fp mixkkfkd ql zolpp qeb Oryfzlk!" Щоб навести ще один швидкий приклад різних можливостей, я створив металеву функцію, яка приймає вхідний рядок і використовує функцію перекладу для повернення тексту, включаючи всі символи, які приймають умляути. Перегляньте металеву функцію Pen [forked] Браяна Расмуссена.
const metal = (str) => { return translate(str, "AOUaou", "ÄÖÜäöü"); }
І якщо надати текст «Motley Crüe rules, rock on dudes!», повертає «Mötley Crüe rüles, röck ön düdes!» Очевидно, що ця функція може використовуватися в різних пародійних формах. Якщо це ви, тоді ця стаття TVTropes має надати вам багато натхнення. Використання CSS із XPath Пам’ятайте про нашу головну причину використання селекторів CSS разом із XPath: CSS майже розуміє, що таке клас, тоді як найкраще, що ви можете зробити з XPath, це порівняння рядків атрибута class. У більшості випадків це спрацює. Але якби ви коли-небудь зіткнулися з ситуацією, коли, скажімо, хтось створив класи з іменами .primaryLinks і .primaryLinks2, а ви використовували XPath для отримання класу .primaryLinks, тоді ви, швидше за все, зіткнулися б з проблемами. Поки немає нічого такого дурного, ви, ймовірно, скористаєтеся XPath. Але мені сумно повідомляти, що я працював у місцях, де люди роблять подібні дурниці. Ось ще одна демонстрація спільного використання CSS і XPath. Він показує, що відбувається, коли ми використовуємо код для запуску XPath на вузлі контексту, який не є вузлом документа. Перегляньте Pen css і xpath разом [forked] Браяна Расмуссена. Запитом CSS є .relatedarticles a, який отримує два елементи a у div, якому призначено клас .relatedarticles. Після цього йдуть три «поганих» запити, тобто запити, які не виконують те, що ми хочемо, щоб вони робили під час роботи з цими елементами як вузлом контексту. Я можу пояснити, чому вони поводяться не так, як ви могли б очікувати. Три погані запити:
//text(): повертає весь текст у документі. //a/text(): повертає весь текст усередині посилань у документі. ./a/text(): не повертає результатів.
Причина таких результатів полягає в тому, що, хоча ваш контекст є елементами, повернутими із запиту CSS, // суперечить всьому документу. Це сильна сторона XPath; CSS не може переходити від вузла до предка, а потім до брата цього предка, а потім переходити до нащадка цього брата. Але XPath може. Тим часом ./ запитує дочірні вузли поточного вузла, де крапка (.) представляє поточний вузол, а коса риска (/) означає перехід до якогось дочірнього вузла — чи є це атрибутом, елементом чи текстом, визначається наступною частиною шляху. Але немає дочірнього елемента, вибраного запитом CSS, тому цей запит також нічого не повертає. У цій останній демонстрації є три хороші запити:
.//текст(), ./текст(), normalize-space(./text()).
Запит normalize-space демонструє використання функції XPath, але також усуває проблему, включену в інші запити. HTML структурований так:
Автоматизація тестування ваших функцій за допомогою Selenium WebDriver
Запит повертає переведення рядка на початку та в кінці текстового вузла,і normalize-space видаляє це. Використання будь-якої функції XPath, яка повертає щось інше, ніж логічне значення, із вхідним XPath, застосовується до інших функцій. Наступна демонстрація показує кілька прикладів: Перегляньте приклади функцій Pen xpath [forked] Браяна Расмуссена. Перший приклад показує проблему, на яку слід звернути увагу. Зокрема, такий код:
document.queryXPaths("substring-after(//a/@href,'https://')");
…повертає один рядок:
"www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/"
Це має сенс, правда? Ці функції повертають не масиви, а лише окремі рядки чи числа. Запуск функції будь-де з кількома результатами повертає лише перший результат. Другий результат показує, чого ми дійсно хочемо:
document.queryCSSSelectors("a").queryXPaths("substring-after(./@href,'https://')");
Який повертає масив із двох рядків:
["www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/","www.smashingmagazine.com/2022/11/automated-test-results-improve-accessibility/"]
Функції XPath можна вкладати так само, як функції в JavaScript. Отже, якщо ми знаємо структуру URL-адреси Smashing Magazine, ми можемо зробити наступне (рекомендується використовувати літерали шаблону): `перекласти( підрядок( substring-after(./@href, ‘www.smashingmagazine.com/’) ,9), '/','')`
Це стає надто складним до такої міри, що потребує коментарів, які описують, що він робить: візьміть всю URL-адресу з атрибута href після www.smashingmagazine.com/, видаліть перші дев’ять символів, а потім перекладіть символ косої риски (/) на ніщо, щоб позбутися кінцевої косої риски. Отриманий масив:
["feature-testing-selenium-webdriver","automated-test-results-improve-accessibility"]
Більше випадків використання XPath XPath дійсно може сяяти в тестуванні. Причину не важко зрозуміти, оскільки XPath можна використовувати для отримання кожного елемента в DOM з будь-якої позиції в DOM, тоді як CSS не може. Ви не можете розраховувати на те, що класи CSS залишатимуться узгодженими в багатьох сучасних системах збірки, але за допомогою XPath ми можемо створювати надійніші відповідності щодо текстового вмісту елемента, незалежно від зміни структури DOM. Було проведено дослідження методів, які дозволяють робити стійкі тести XPath. Немає нічого гіршого, ніж те, що тести луснуть і провалюються лише тому, що селектор CSS більше не працює через те, що щось було перейменовано або видалено. XPath також чудово підходить для вилучення кількох локаторів. Існує більше одного способу використання запитів XPath для відповідності елементу. Те саме стосується CSS. Але запити XPath можуть детальніше вивчати речі, що обмежує те, що повертається, дозволяючи вам знаходити конкретну відповідність, де може бути кілька можливих відповідностей. Наприклад, ми можемо використовувати XPath, щоб повернути певний елемент h2, який міститься всередині div, який слідує безпосередньо за однорідним div, який, у свою чергу, містить дочірній елемент зображення з атрибутом data-testID="leader":
не отримуйте цей заголовок
Також не отримуйте цей заголовок
Заголовок для зображення виноски
Це запит: document.queryXPaths(` //div[ наступний брат::div[1] /img[@data-testID='лідер'] ] /h2/ текст() `);
Давайте запустимо демонстрацію, щоб побачити, як це все поєднується: Перегляньте Pen Complex H2 Query [forked] Браяна Расмуссена. Отже, так. Існує багато можливих шляхів до будь-якого елемента в тесті за допомогою XPath. XSLT 1.0 застаріла Раніше я згадував, що команда Chrome планує видалити підтримку XSLT 1.0 із браузера. Це важливо, оскільки XSLT 1.0 використовує орієнтоване на XML програмування для перетворення документів, яке, у свою чергу, покладається на XPath 1.0, який є в більшості браузерів. Коли це станеться, ми втратимо ключовий компонент XPath. Але враховуючи той факт, що XPath дійсно чудово підходить для написання тестів, я вважаю малоймовірним, що XPath в цілому зникне найближчим часом. Тим не менш, я помітив, що люди цікавляться функцією, коли її забирають. І це, звичайно, вірно у випадку XSLT 1.0, який застарів. У Hacker News точиться ціла дискусія, наповнена аргументами проти припинення підтримки. Сама публікація є чудовим прикладом створення фреймворку для ведення блогу за допомогою XSLT. Виможете прочитати обговорення для себе, але воно потрапляє в те, як JavaScript може використовуватися як прокладка для XLST для обробки таких випадків. Я також бачив пропозиції, що браузери повинні використовувати SaxonJS, який є портом для двигунів Saxon XSLT, XQUERY і XPath JavaScript. Це цікава ідея, особливо тому, що Saxon-JS реалізує поточну версію цих специфікацій, тоді як немає жодного браузера, який реалізує будь-яку версію XPath або XSLT після 1.0, і жодного, який реалізує XQuery. Я звернувся до Норма Тові-Уолша в Saxonica, компанії, що стоїть за SaxonJS та іншими версіями механізму Saxon. Він сказав: «Якби будь-який постачальник браузерів був зацікавлений у тому, щоб SaxonJS став відправною точкою для інтеграції сучасних технологій XML у браузер, ми б із задоволенням обговорили це з ними», — Норм Тові-Уолш.
Але також додав: "Я був би дуже здивований, якби хтось подумав, що взяти SaxonJS у його поточній формі та вставити його в збірку браузера без змін було б ідеальним підходом. Постачальник браузера, завдяки тому факту, що він створює веб-переглядач, міг би підійти до інтеграції на набагато глибшому рівні, ніж ми можемо "ззовні"" — Норм Тові-Уолш.
Варто зазначити, що коментарі Тові-Уолша з’явилися приблизно за тиждень до оголошення про припинення підтримки XSLT. Висновок Я міг би продовжувати і продовжувати. Але я сподіваюся, що це продемонструвало силу XPath і дало вам багато прикладів того, як використовувати його для досягнення великих результатів. Це чудовий приклад старішої технології в стеку браузера, яка все ще має багато корисності сьогодні, навіть якщо ви ніколи не знали про її існування або ніколи не думали скористатися нею. Подальше читання
«Підвищення стійкості автоматизованих веб-тестів за допомогою природної мови» (цифрова бібліотека ACM), автори Марун Айлі, Юсеф Бакуні, Надер Джаллул і Ріма Кілані. У цій статті наведено багато прикладів XPath для написання стійких тестів. XPath (MDN) Це чудове місце для початку, якщо ви хочете отримати технічне пояснення, де детально описано, як працює XPath. Підручник XPath (ZVON) Я вважаю цей підручник найбільш корисним у моєму навчанні завдяки великій кількості прикладів і чітких пояснень. XPatherЦей інтерактивний інструмент дозволяє працювати безпосередньо з кодом.