Jag har varit i front-end-utveckling tillräckligt länge för att se en trend genom åren: yngre utvecklare som arbetar med ett nytt paradigm för programmering utan att förstå det historiska sammanhanget. Det är förstås fullt förståeligt att inte veta något. Webben är en mycket stor plats med en mångfald av färdigheter och specialiteter, och vi vet inte alltid vad vi inte vet. Att lära sig inom detta område är en pågående resa snarare än något som händer en gång och tar slut. Exempel: Någon i mitt team frågade om det var möjligt att se om användare navigerar bort från en viss flik i användargränssnittet. Jag påpekade JavaScripts beforeunload-händelse. Men de som har tagit itu med detta tidigare vet att detta är möjligt eftersom de har drabbats av varningar om osparad data på andra webbplatser, för vilka före unload är ett typiskt användningsfall. Jag har också påpekat sidan Göm och synlighet Ändra händelser för min kollega för god åtgärd. Hur visste jag om det? För att det kom upp i ett annat projekt, inte för att jag studerade det när jag först lärde mig JavaScript. Faktum är att moderna front-end-ramverk står på axlarna av de teknikjättar som föregick dem. De abstraherar utvecklingsmetoder, ofta för en bättre utvecklarupplevelse som minskar, eller till och med eliminerar, behovet av att känna till eller beröra vad som traditionellt har varit väsentliga front-end-koncept som alla förmodligen borde känna till. Tänk på CSS Object Model (CSSOM). Du kan förvänta dig att alla som arbetar med CSS och JavaScript har en massa praktisk CSSOM-erfarenhet, men det kommer inte alltid att vara fallet. Det fanns ett React-projekt för en e-handelssida som jag arbetade på där vi behövde ladda en stilmall för den för närvarande valda betalningsleverantören. Problemet var att stilarket laddades på varje sida när det egentligen bara behövdes på en specifik sida. Utvecklaren som fick i uppdrag att få detta att hända hade aldrig laddat en stilmall dynamiskt. Återigen, detta är helt förståeligt när React abstraherar bort det traditionella tillvägagångssättet du kanske har längtat efter. CSSOM är förmodligen inget du behöver i ditt dagliga arbete. Men det är troligt att du kommer att behöva interagera med det någon gång, även i ett enstaka fall. Dessa erfarenheter inspirerade mig att skriva den här artikeln. Det finns många befintliga webbfunktioner och tekniker i naturen som du kanske aldrig rör direkt i ditt dagliga arbete. Kanske är du ganska ny på webbutveckling och är helt enkelt omedveten om dem eftersom du är genomsyrad av abstraktionen av ett specifikt ramverk som inte kräver att du känner till det djupt, eller ens alls. Jag talar specifikt om XML, som många av oss vet är ett uråldrigt språk som inte är helt olik HTML. Jag tar upp detta på grund av de senaste WHATWG-diskussioner som föreslår att en betydande del av XML-stacken känd som XSLT-programmering bör tas bort från webbläsare. Det här är precis den sortens äldre, befintliga teknik vi har haft i flera år som skulle kunna användas för något så praktiskt som CSSOM-situationen som mitt team var i. Har du arbetat med XSLT tidigare? Låt oss se om vi lutar oss mycket mot denna äldre teknik och utnyttjar dess funktioner utanför XML-sammanhang för att ta itu med verkliga problem idag. XPath: Central API Den viktigaste XML-tekniken som kanske är den mest användbara utanför ett rakt XML-perspektiv är XPath, ett frågespråk som låter dig hitta vilken nod eller attribut som helst i ett uppmärkningsträd med ett rotelement. Jag har en personlig tillgivenhet för XSLT, men det är också beroende av XPath, och personlig tillgivenhet måste läggas åt sidan när det gäller rangordning. Argumentet för att ta bort XSLT nämner inte XPath, så jag antar att det fortfarande är tillåtet. Det är bra eftersom XPath är det centrala och viktigaste API:et i denna svit av teknologier, speciellt när man försöker hitta något att använda utanför normal XML-användning. Det är viktigt eftersom, även om CSS-väljare kan användas för att hitta de flesta av elementen på din sida, kan de inte hitta dem alla. Dessutom kan CSS-väljare inte användas för att hitta ett element baserat på dess nuvarande position i DOM. XPath kan. Nu kanske några av er som läser det här känner till XPath, och andra kanske inte. XPath är ett ganska stort teknikområde, och jag kan inte riktigt lära ut alla grunderna och även visa dig coola saker att göra med det i en enda artikel som denna. Jag försökte faktiskt skriva den artikeln, men den genomsnittliga Smashing Magazine-publikationen går inte över 5 000 ord. Jag var redan på mer än2 000 ord medan bara halvvägs genom grunderna. Så jag ska börja göra coola saker med XPath och ge dig några länkar som du kan använda för grunderna om du tycker att det här är intressant. Kombinera XPath och CSS XPath kan göra många saker som CSS-väljare inte kan när de frågar efter element. Men CSS-väljare kan också göra några saker som XPath inte kan, nämligen fråga element efter klassnamn.
CSS XPath .myClass /*[innehåller(@klass, "minKlass")]
I det här exemplet frågar CSS element som innehåller ett .myClass-klassnamn. Samtidigt frågar XPath-exemplet element som innehåller en attributklass med strängen "myClass". Med andra ord, den väljer element med myClass i vilket attribut som helst, inklusive element med .myClass classname — såväl som element med "myClass" i strängen, som .myClass2. XPath är bredare i den meningen. Så nej. Jag föreslår inte att vi borde kasta ut CSS och börja välja alla element via XPath. Det är inte meningen. Poängen är att XPath kan göra saker som CSS inte kan och fortfarande kan vara väldigt användbara, även om det är en äldre teknik i webbläsarstacken och kanske inte verkar självklart vid första anblicken. Låt oss använda de två teknologierna tillsammans, inte bara för att vi kan, utan för att vi kommer att lära oss något om XPath i processen, vilket gör det till ytterligare ett verktyg i din stack – ett som du kanske inte visste har funnits där hela tiden! Problemet är att JavaScripts document.evaluate-metod och de olika frågeväljarmetoderna vi använder med CSS-API:erna för JavaScript är inkompatibla. Jag har skapat ett kompatibelt förfrågnings-API för att komma igång, men jag har visserligen inte tänkt så mycket på det eftersom det är en avvikelse från vad vi gör här. Här är ett ganska enkelt fungerande exempel på en återanvändbar frågekonstruktor: Se Pen queryXPath [klyfta] av Bryan Rasmussen. Jag har lagt till två metoder på dokumentobjektet: queryCSSSelectors (som i huvudsak är querySelectorAll) och queryXPaths. Båda dessa returnerar ett queryResults-objekt:
{ queryType: noder | sträng | nummer | boolesk, resultat: alla[] // html-element, xml-element, strängar, siffror, booleaner, queryCSSSelectors: (query: string, amend: boolean) => queryResults, queryXpaths: (query: string, amend: boolean) => queryResults }
Funktionerna queryCSSSelectors och queryXpaths kör frågan du ger dem över elementen i resultatmatrisen, så länge resultatmatrisen är av typen noder, förstås. Annars kommer det att returnera en queryResult med en tom array och en typ av noder. Om egenskapen amend är satt till true kommer funktionerna att ändra sina egna queryResults. Detta får under inga omständigheter användas i en produktionsmiljö. Jag gör det på detta sätt enbart för att visa de olika effekterna av att använda de två fråge-API:erna tillsammans. Exempel på frågor Jag vill visa några exempel på olika XPath-frågor som visar några av de kraftfulla saker de kan göra och hur de kan användas i stället för andra tillvägagångssätt. Det första exemplet är //li/text(). Detta frågar alla li-element och returnerar deras textnoder. Så om vi skulle fråga efter följande HTML:
- en
- två
- tre
...det här är vad som returneras:
{"queryType":"xpathEvaluate","results":["one","two","three"],"resultType":"string"}
Med andra ord får vi följande array: ["en","två","tre"]. Normalt skulle du fråga efter li-elementen för att få det, förvandla resultatet av den frågan till en array, mappa arrayen och returnera textnoden för varje element. Men vi kan göra det mer kortfattat med XPath: document.queryXPaths("//li/text()").results.
Lägg märke till att sättet att få en textnod är att använda text(), som ser ut som en funktionssignatur — och det är det. Den returnerar textnoden för ett element. I vårt exempel finns det tre li-element i uppmärkningen, var och en innehåller text ("en", "två" och "tre").
Låt oss titta på ytterligare ett exempel på en text()-fråga. Anta att detta är vår uppmärkning:
Låt oss skriva en fråga som returnerar href-attributvärdet: document.queryXPaths("//a[text() = 'Logga in']/@href").results.
Detta är en XPath-fråga på det aktuella dokumentet, precis som i det förra exemplet, men den här gången returnerar vi href-attributet för en länk (ett element) som innehåller texten "Sign In". Den faktiska tillbakaresultatet är ["/login.html"]. Översikt över XPath-funktioner Det finns ett antal XPath-funktioner, och du är förmodligen inte bekant med dem. Det finns flera, tycker jag, som är värda att veta om, inklusive följande:
starts-withOm en text börjar med ett visst annat textexempel, starts-with(@href, 'http:') returnerar sant om ett href-attribut börjar med http:. containsOm en text innehåller ett särskilt annat textexempel returnerar contains(text(), "Smashing Magazine") sant om en textnod innehåller orden "Smashing Magazine" var som helst. countReturnerar ett antal av hur många matchningar det finns till en fråga. Till exempel returnerar count(//*[starts-with(@href, 'http:']) ett antal av hur många länkar i kontextnoden som har element med ett href-attribut som innehåller texten som börjar med http:. substring Fungerar som JavaScript-substring, förutom att du skickar strängen som ett argument. Till exempel, substring("min text", 2, 4) returnerar "y t". substring-beforeReturnerar delen av en sträng före en annan sträng. Till exempel, substing-before("min text", " ") returnerar "min". På liknande sätt returnerar substring-before("hej","bye") en tom sträng. substring-afterReturnerar delen av en sträng efter en annan sträng. Till exempel, substing-after("min text", " ") returnerar "text". På liknande sätt returnerar substring-after("hej","bye") en tom sträng. normalize-space Returnerar argumentsträngen med blanksteg normaliserad genom att ta bort inledande och efterföljande blanksteg och ersätta sekvenser av blankstegstecken med ett enda blanksteg. notReturnerar ett booleskt sant om argumentet är falskt, annars falskt. trueReturnerar boolesk sant. falseReturnerar boolesk false. concat Samma sak som JavaScript concat, förutom att du inte kör det som en metod på en sträng. Istället lägger du in alla strängar du vill sammanfoga. string-lengthDetta är inte samma sak som JavaScript string-length, utan returnerar snarare längden på strängen den ges som ett argument. translateThis tar en sträng och ändrar det andra argumentet till det tredje argumentet. Till exempel, translate("abcdef", "abc", "XYZ") matar ut XYZdef.
Bortsett från dessa speciella XPath-funktioner finns det ett antal andra funktioner som fungerar precis som deras JavaScript-motsvarigheter – eller motsvarigheter på i princip vilket programmeringsspråk som helst – som du förmodligen också skulle tycka är användbara, såsom golv, tak, rund, summa och så vidare. Följande demo illustrerar var och en av dessa funktioner: Se Pen XPath Numeriska funktioner [förklädd] av Bryan Rasmussen. Observera att, liksom de flesta av strängmanipuleringsfunktionerna, tar många av de numeriska en enda ingång. Detta beror naturligtvis på att de är tänkta att användas för frågor, som i det senaste XPath-exemplet: //li[floor(text()) > 250]/@val
Om du använder dem, som de flesta av exemplen gör, kommer du att köra den på den första noden som matchar sökvägen. Det finns också några typkonverteringsfunktioner du förmodligen bör undvika eftersom JavaScript redan har sina egna typkonverteringsproblem. Men det kan finnas tillfällen då du vill konvertera en sträng till ett nummer för att kontrollera det mot något annat nummer. Funktioner som anger typen av något är boolean, nummer, sträng och nod. Dessa är de viktiga XPath-datatyperna. Och som du kanske föreställer dig kan de flesta av dessa funktioner användas på datatyper som inte är DOM-noder. Till exempel tar substring-after en sträng som vi redan har täckt, men det kan vara strängen från ett href-attribut. Det kan också bara vara en sträng:
const testSubstringAfter = document.queryXPaths("substring-after('hej världen',' ')");
Uppenbarligen kommer detta exempel att ge oss tillbaka resultatmatrisen som ["värld"]. För att visa detta i aktion har jag gjort en demosida som använder funktioner mot saker som inte är DOM-noder: Se Pen queryXPath [klyfta] av Bryan Rasmussen. Du bör notera den överraskande aspekten av översättningsfunktionen, som är att om du har ett tecken i det andra argumentet (dvs listan över tecken du vill översätta) och inget matchande tecken att översätta till, tas det tecknet bort från utdata. Alltså detta:
translate('Hej, mitt namn är Inigo Montoya, du dödade min far, förbered dig på att dö','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','*')
…resultat i strängen, inklusive mellanslag: [" * * ** "]
Detta betyder att bokstaven "a" översätts till en asterisk (*), men alla andra tecken som inte har en översättning med tanke på målsträngen tas bort helt. Vitrymden är allt vi har kvarmellan de översatta "a"-tecknen. Återigen, denna fråga:
translate('Hej, mitt namn är Inigo Montoya, du dödade min far, förbered dig på att dö','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','*******************************************************')")
…har inte problemet och ger ett resultat som ser ut så här:
"***** ** **** ** ***** ******* *** ****** ** ****** ******* ** ***"
Det kanske slår dig att det inte finns något enkelt sätt i JavaScript att göra exakt vad XPath-översättningsfunktionen gör, även om för många användningsfall kan replaceAll med reguljära uttryck hantera det. Du kan använda samma tillvägagångssätt som jag har visat, men det är suboptimalt om allt du vill är att översätta strängarna. Följande demo omsluter XPaths översättningsfunktion för att tillhandahålla en JavaScript-version: Se översättningsfunktionen för penna [klyftad] av Bryan Rasmussen. Var kan du använda något sådant? Överväg Caesar Cipher-kryptering med en tre-placerad offset (t.ex. top-of-the-line kryptering från 48 f.Kr.):
translate("Caesar planerar att korsa Rubicon!", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", "XYZABCDEFGHIJKLMNOPQRSTUVWxyzabcdefghijklmnopqrstuvw")
Inmatningstexten "Caesar planerar att korsa Rubicon!" resulterar i "Zxbpxo fp mixkkfkd ql zolpp qeb Oryfzlk!" För att ge ytterligare ett snabbt exempel på olika möjligheter gjorde jag en metallfunktion som tar en stränginmatning och använder en översättningsfunktion för att returnera texten, inklusive alla tecken som tar omljud. Se pennans metallfunktion [gaffelformad] av Bryan Rasmussen.
const metal = (str) => { return translate(str, "AOUaou","ÄÖÜäöü"); }
Och om man får texten "Motley Crue ruler, rock on dudes!", returnerar "Mötley Crüe rüles, röck ön düdes!" Uppenbarligen kan man ha alla möjliga parodiska användningar av denna funktion. Om det är du, borde den här TVTropes-artikeln ge dig massor av inspiration. Använder CSS med XPath Kom ihåg vår främsta anledning till att använda CSS-väljare tillsammans med XPath: CSS förstår i stort sett vad en klass är, medan det bästa du kan göra med XPath är strängjämförelser av class-attributet. Det kommer att fungera i de flesta fall. Men om du någonsin skulle hamna i en situation där, säg, någon skapade klasser som heter .primaryLinks och .primaryLinks2 och du använde XPath för att få .primaryLinks-klassen, då skulle du troligen stöta på problem. Så länge det inte finns något dumt sådant, skulle du förmodligen använda XPath. Men jag är ledsen att rapportera att jag har arbetat på platser där folk gör den typen av fåniga saker. Här är en annan demo som använder CSS och XPath tillsammans. Den visar vad som händer när vi använder koden för att köra en XPath på en kontextnod som inte är dokumentets nod. Se Pen css och xpath tillsammans [klyftade] av Bryan Rasmussen. CSS-frågan är .relatedarticles a, som hämtar de två a-elementen i en div som har tilldelats en .relatedarticles-klass. Efter det kommer tre "dåliga" frågor, det vill säga frågor som inte gör det vi vill att de ska göra när de körs med dessa element som kontextnod. Jag kan förklara varför de beter sig annorlunda än du kanske förväntar dig. De tre dåliga frågorna i fråga är:
//text(): Returnerar all text i dokumentet. //a/text(): Returnerar all text inuti länkar i dokumentet. ./a/text(): Ger inga resultat.
Anledningen till dessa resultat är att medan ditt sammanhang är ett element som returneras från CSS-frågan, går // emot hela dokumentet. Detta är styrkan hos XPath; CSS kan inte gå från en nod upp till en förfader och sedan till ett syskon till den förfadern, och gå ner till en ättling till det syskonen. Men XPath kan. Samtidigt frågar ./ den aktuella nodens underordnade, där punkten (.) representerar den aktuella noden, och snedstrecket (/) representerar att gå till någon underordnad nod – om det är ett attribut, element eller text bestäms av nästa del av sökvägen. Men det finns inget underordnat element som valts av CSS-frågan, så den frågan returnerar heller ingenting. Det finns tre bra frågor i den senaste demon:
.//text(), ./text(), normalize-space(./text()).
Normalize-space-frågan visar XPath-funktionsanvändning, men åtgärdar också ett problem som ingår i de andra frågorna. HTML-koden är uppbyggd så här:
Automatisera din funktionstestning med Selenium WebDriver
Frågan returnerar en radmatning i början och slutet av textnoden,och normalize-space tar bort detta. Att använda valfri XPath-funktion som returnerar något annat än en boolean med en ingång XPath gäller för andra funktioner. Följande demo visar ett antal exempel: Se Exemplen på Pen xpath-funktioner [forked] av Bryan Rasmussen. Det första exemplet visar ett problem du bör se upp med. Närmare bestämt följande kod:
document.queryXPaths("substring-after(//a/@href,'https://')");
…returerar en sträng:
"www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/"
Det är vettigt, eller hur? Dessa funktioner returnerar inte matriser utan snarare enstaka strängar eller enstaka nummer. Att köra funktionen var som helst med flera resultat returnerar bara det första resultatet. Det andra resultatet visar vad vi verkligen vill ha:
document.queryCSSSelectors("a").queryXPaths("substring-after(./@href,'https://')");
Vilket returnerar en array med två strängar:
["www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/","www.smashingmagazine.com/2022/11/automated-test-results-improve-accessibility/"]
XPath-funktioner kan kapslas precis som funktioner i JavaScript. Så om vi känner till Smashing Magazine URL-struktur kan vi göra följande (användning av mallbokstavar rekommenderas): `översätta( delsträng( substring-after(./@href, 'www.smashingmagazine.com/') ,9), '/','')".
Det här blir lite för komplicerat i den utsträckningen att det behöver kommentarer som beskriver vad det gör: ta hela URL:en från href-attributet efter www.smashingmagazine.com/, ta bort de första nio tecknen och översätt sedan snedstrecket (/) till ingenting för att bli av med det avslutande snedstrecket. Den resulterande arrayen:
["feature-testing-selenium-webdriver","automated-test-results-improve-accessibility"]
Fler användningsfall för XPath XPath kan verkligen lysa i tester. Orsaken är inte svår att se, eftersom XPath kan användas för att hämta alla element i DOM, från vilken position som helst i DOM, medan CSS inte kan. Du kan inte räkna med att CSS-klasser förblir konsekventa i många moderna byggsystem, men med XPath kan vi göra mer robusta matchningar om vad textinnehållet i ett element är, oavsett en förändrad DOM-struktur. Det har gjorts forskning om tekniker som gör att du kan göra motståndskraftiga XPath-tester. Inget är värre än att tester flagnar ut och misslyckas bara för att en CSS-väljare inte längre fungerar för att något har bytt namn eller tagits bort. XPath är också riktigt bra på extraktion av flera lokaliseringsenheter. Det finns mer än ett sätt att använda XPath-frågor för att matcha ett element. Detsamma gäller med CSS. Men XPath-frågor kan borra i saker på ett mer riktat sätt som begränsar vad som returneras, vilket gör att du kan hitta en specifik matchning där det kan finnas flera möjliga matchningar. Till exempel kan vi använda XPath för att returnera ett specifikt h2-element som finns inuti en div som omedelbart följer en syskon-div som i sin tur innehåller ett underordnat bildelement med ett data-testID="leader"-attribut på sig:
förstår inte den här rubriken
Förstår inte heller den här rubriken
Rubriken för ledarbilden
Det här är frågan: document.queryXPaths(` //div[ följande syskon::div[1] /img[@data-testID='ledare'] ] /h2/ text() `);
Låt oss ta en demo för att se hur allt hänger ihop: Se Pen Complex H2 Query [förklädd] av Bryan Rasmussen. Så ja. Det finns många möjliga vägar till alla element i ett test med XPath. XSLT 1.0 Utfasning Jag nämnde tidigt att Chrome-teamet planerar att ta bort XSLT 1.0-stödet från webbläsaren. Det är viktigt eftersom XSLT 1.0 använder XML-fokuserad programmering för dokumenttransformation som i sin tur förlitar sig på XPath 1.0, vilket är vad som finns i de flesta webbläsare. När det händer kommer vi att förlora en nyckelkomponent i XPath. Men med tanke på att XPath är riktigt bra för att skriva tester, tycker jag det är osannolikt att XPath som helhet kommer att försvinna någon gång snart. Som sagt, jag har märkt att folk blir intresserade av en funktion när den tas bort. Och det är verkligen sant när XSLT 1.0 fasas ut. Det pågår en hel diskussion på Hacker News fylld med argument mot avskrivningen. Inlägget i sig är ett bra exempel på att skapa ett bloggramverk med XSLT. Dukan läsa diskussionen själv, men det kommer in på hur JavaScript kan användas som ett shim för XLST för att hantera den typen av fall. Jag har också sett förslag på att webbläsare ska använda SaxonJS, som är en port till JavaScripts Saxon XSLT-, XQUERY- och XPath-motorer. Det är en intressant idé, särskilt som Saxon-JS implementerar den nuvarande versionen av dessa specifikationer, medan det inte finns någon webbläsare som implementerar någon version av XPath eller XSLT utöver 1.0, och ingen som implementerar XQuery. Jag nådde ut till Norm Tovey-Walsh på Saxonica, företaget bakom SaxonJS och andra versioner av Saxon-motorn. Han sa: "Om någon webbläsarleverantör var intresserad av att ta SaxonJS som utgångspunkt för att integrera modern XML-teknik i webbläsaren, skulle vi bli glada över att diskutera det med dem." - Norm Tovey-Walsh
Men tillade också: "Jag skulle bli mycket förvånad om någon trodde att det skulle vara det perfekta tillvägagångssättet att ta SaxonJS i sin nuvarande form och släppa det i webbläsarbygget oförändrat. En webbläsarleverantör, på grund av det faktum att de bygger webbläsaren, kan närma sig integrationen på en mycket djupare nivå än vad vi kan "från utsidan". - Norm Tovey-Walsh
Det är värt att notera att Tovey-Walshs kommentarer kom ungefär en vecka före XSLT-avskrivningsmeddelandet. Slutsats Jag kunde fortsätta och fortsätta. Men jag hoppas att detta har visat kraften hos XPath och gett dig massor av exempel som visar hur du använder det för att uppnå fantastiska saker. Det är ett perfekt exempel på äldre teknik i webbläsarstacken som fortfarande har gott om nytta idag, även om du aldrig har vetat att den fanns eller aldrig övervägt att nå efter den. Ytterligare läsning
"Enhancing the Resiliency of Automated Web Tests with Natural Language" (ACM Digital Library) av Maroun Ayli, Youssef Bakouny, Nader Jalloul och Rima KilanyDen här artikeln ger många XPath-exempel för att skriva motståndskraftiga tester. XPath (MDN) Det här är ett utmärkt ställe att börja om du vill ha en teknisk förklaring som beskriver hur XPath fungerar. XPath Tutorial (ZVON) Jag har tyckt att denna handledning är den mest användbara i mitt eget lärande, tack vare en mängd exempel och tydliga förklaringar. XPatherDet här interaktiva verktyget låter dig arbeta direkt med koden.