IntroductiontoChromeDevTools
ChromeDevToolsisasetoftoolsbuiltdirectlyintoChromium-basedbrowserssuchasChrome,Opera,andMicrosoftEdgetohelpdevelopersdebugandstudywebsites.
WithChromeDevTools,developerscangaindeeperaccesstowebsitesandbeableto:
- InspectelementsintheDOM
- EditelementsandCSSonthefly
- Checkandmonitorwebsiteperformance
- Simulatetheuser’sgeographiclocation
- Simulatefaster/slowernetworkspeeds
- ExecuteanddebugJavaScript
- Viewconsolelog
- etc.
Selenium4ChromeDevToolsAPI
Seleniumisacomprehensivesetoftoolsandlibrariesthatsupportwebbrowserautomation.Selenium4addsnativesupportfortheChromeDevToolsAPI.WiththesenewAPIs,ourtestscannow:
- Captureandmonitornetworktrafficandperformance
- Simulatedgeolocationforlocation-awaretesting,localizationandinternationalizationtesting
- Changedevicemodesandtestyourapp’sresponsiveness
Thisisjustthetipoftheiceberg!
Selenium4introducesthenewChromiumDriverclass,whichincludestwomethodsforaccessingChromeDevTools:getDevTools()andexecuteCdpCommand().
ThegetDevTools()methodreturnsanewDevToolsobjectthatallowsyoutosendbuilt-inSeleniumcommandsforCDPusingthesend()method.ThesecommandsarewrappermethodsthatmakecallingCDPfunctionsclearerandeasier.
TheexecuteCdpCommand()methodalsoallowsyoutoexecuteCDPmethods,butismoreprimitive.InsteadofusingawrappedAPI,itallowsyoutodirectlypassinaChromeDevToolscommandandtheargumentstothatcommand.IfthereisnoSeleniumwrapperAPIforaCDPcommand,oryouwanttocallitdifferentlythantheSeleniumAPI,youcanuseexecuteCdpCommand().
Chromium-baseddriverslikeChromeDriverandEdgeDrivernowinheritfromChromiumDriver,soyoucanaccesstheSeleniumCDPAPIfromthesedriversaswell.
Let’sexplorehowyoucanleveragethesenewSelenium4APIstosolvevarioususecases.
Emulateddevicemode
Mostoftheappswebuildtodayareresponsivetomeettheneedsofendusersfromavarietyofplatforms,devices(e.g.mobile,tablet,wearables,desktop)andscreenorientations.
Astesters,wemaywanttoplaceourappsindifferentsizestotriggerresponsivenessintheapp.
HowcanweachievethisusingSelenium’snewCDPfunctionality?
TheCDPcommandformodifyingdevicemetricsisEmulation.setDeviceMetricsOverride,andthiscommandrequiresinputofwidth,height,mobiledeviceflag,anddevicescalingfactor.Thesefourkeysarerequiredinthisscenario,buttherearesomeoptionalkeys.
InourSeleniumtests,wecanusetheDevTools::send()methodandusethebuilt-insetDeviceMetricsOverride()command,butthisSeleniumAPIaccepts12parameters-inadditiontothe4requiredparameters,thereare8optionalonesparameter.Foranyofthese8optionalparametersthatwedon’tneedtosend,wecanpassOptional.empty().
However,tosimplifythisprocessandonlypasstherequiredparameters,IwillusetheoriginalexecuteCdpCommand()methodinthecodebelow.
packagecom.devtools; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importjava.util.HashMap; importjava.util.Map; publicclassSetDeviceMode{ finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); ChromeDriverdriver; driver=newChromeDriver(); DevToolsdevTools=driver.getDevTools(); devTools.createSession(); MapdeviceMetrics=newHashMap() {<!---->{ put("width",600); put("height",1000); put("mobile",true); put("deviceScaleFactor",50); }}; driver.executeCdpCommand("Emulation.setDeviceMetricsOverride",deviceMetrics); driver.get("https://www.google.com"); } }
Online19,Icreateamapcontainingthekeysrequiredforthiscommand.
Thenonline26,IcalltheexecuteCdpCommand()method,passingtwoparameters:thecommandname”Emulation.setDeviceMetricsOverride”,andthedevicemetricsmapcontainingtheparameters.
Online27,Iopenthe”Google”homepagerenderedwiththespecsIprovided,asshownbelow.
WithasolutionlikeApplitoolsEyes,wecannotonlyquicklytestondifferentviewportsusingthesenewSeleniumcommands,butwecanalsomaintainanyinconsistenciesatscale.EyesissmartenoughnottoreportincorrectresultsforsmallandimperceptiblechangesintheUIduetodifferentbrowsersandviewports.
Simulatedlocation
Inmanycasesweneedtotestspecificlocation-basedfeaturessuchasoffers,location-basedpricing,etc.ForthiswecanusetheDevToolsAPItosimulatethelocation.
@Test publicvoidmockLocation(){ devTools.send(Emulation.setGeolocationOverride( Optional.of(48.8584), Optional.of(2.2945), Optional.of(100))); driver.get("https://mycurrentlocation.net/"); try{ Thread.sleep(30000); }catch(InterruptedExceptione){ e.printStackTrace(); } }
Simulatenetworkspeed
ManyusersaccesswebapplicationsthroughhandhelddevicesconnectedtoWi-Fiorcellularnetworks.Itiscommontoexperienceaweaknetworksignalandthereforeaslowinternetconnection.
Insituationswhereyouhaveaslowinternetconnection(2G)orintermittentoutages,itmaybeimportanttotesthowyourapplicationbehavesundersuchconditions.
TheCDPcommandthatfakesnetworkconnectionsisNetwork.emulateNetworkConditions.Informationabouttherequiredandoptionalparametersofthiscommandcanbefoundinthedocumentation.
ByaccessingChromeDevTools,youcansimulatethesescenarios.Let’sseehowtodothis.
packagecom.devtools; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importorg.openqa.selenium.devtools.network.Network; importorg.openqa.selenium.devtools.network.model.ConnectionType; importjava.util.HashMap; importjava.util.Map; importjava.util.Optional; publicclassSetNetwork{ finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); ChromeDriverdriver; driver=newChromeDriver(); DevToolsdevTools=driver.getDevTools(); devTools.createSession(); devTools.send(Network.enable(Optional.empty(),Optional.empty(),Optional.empty())); devTools.send(Network.emulateNetworkConditions( false, 20, 20, 50, Optional.of(ConnectionType.CELLULAR2G) )); driver.get("https://www.google.com"); } }
Online21,wegettheDevToolsobjectbycallingthegetDevTools()method.Wethencallthesend()methodtoenableNetworkandcallthesend()methodagainpassingthebuilt-incommandNetwork.emulateNetworkConditions()andtheparameterswewanttosendwiththiscommand.
Finally,weopentheGooglehomepageusingsimulatednetworkconditions.
CaptureHTTPrequests
UsingDevTools,wecancaptureHTTPrequestsmadebyapplicationsandaccessmethods,data,headers,andmore.
Let’sseehowtocapturetheHTTPrequest,URI,andrequestmethodusingsamplecode.
packagecom.devtools; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importorg.openqa.selenium.devtools.network.Network; importjava.util.Optional; publicclassCaptureNetworkTraffic{ privatestaticChromeDriverdriver; privatestaticDevToolschromeDevTools; finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); driver=newChromeDriver(); chromeDevTools=driver.getDevTools(); chromeDevTools.createSession(); chromeDevTools.send(Network.enable(Optional.empty(),Optional.empty(),Optional.empty())); chromeDevTools.addListener(Network.requestWillBeSent(), entry->{ System.out.println("RequestURI:"+entry.getRequest().getUrl()+"\ " +"Withmethod:"+entry.getRequest().getMethod()+"\ "); entry.getRequest().getMethod(); }); driver.get("https://www.google.com"); chromeDevTools.send(Network.disable()); } }
TheCDPcommandtostartcapturingnetworktrafficisNetwork.enable.Informationabouttherequiredandoptionalparametersofthiscommandcanbefoundinthedocumentation.
Inourcode,line22usestheDevTools::send()methodtosendtheNetwork.enableCDPcommandtoenablenetworktrafficcapture.
Line23addsalistenerthatlistensforallrequestssentbytheapplication.Foreachrequestcapturedbytheapplication,weextracttheURLusinggetRequest().getUrl()andtheHTTPmethodusinggetRequest().getMethod().
Online29,weopentheGooglehomepageandprinttheURIandHTTPmethodofallrequestsmadebythispageontheconsole.
Oncewehavefinishedcapturingtherequest,wecansendtheNetwork.disableCDPcommandtostopcapturingnetworktraffic,asshownonline30.
InterceptionofHTTPresponses
InordertointercepttheresponsewewillusetheNetwork.responseReceivedevent.ThiseventistriggeredwhenanHTTPresponseisavailable,wecanlistenfortheURL,responseheaders,responsecode,etc.Togettheresponsebody,usetheNetwork.getResponseBodymethod.
@Test publicvoidvalidateResponse(){ finalRequestId[]requestIds=newRequestId[1]; devTools.send(Network.enable(Optional.of(100000000),Optional.empty(),Optional.empty())); devTools.addListener(Network.responseReceived(),responseReceived->{ if(responseReceived.getResponse().getUrl().contains("api.zoomcar.com")){ System.out.println("URL:"+responseReceived.getResponse().getUrl()); System.out.println("Status:"+responseReceived.getResponse().getStatus()); System.out.println("Type:"+responseReceived.getType().toJson()); responseReceived.getResponse().getHeaders().toJson().forEach((k,v)->System.out.println((k+":"+v))); requestIds[0]=responseReceived.getRequestId(); System.out.println("ResponseBody:\ "+devTools.send(Network.getResponseBody(requestIds[0])).getBody()+"\ "); } }); driver.get("https://www.zoomcar.com/bangalore"); driver.findElement(By.className("search")).click(); }
AccessConsoleLog
Weallrelyonlogsfordebuggingandanalyzingfailures.Whentestingandworkingwithapplicationswithspecificdataorspecificconditions,logscanhelpusdebugandcaptureerrormessages,providingadditionalinsightspublishedintheconsoletabofChromeDevTools.
WecancaptureconsolelogsthroughourSeleniumscriptbycallingtheCDPlogcommandasshownbelow.
packagecom.devtools; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importorg.openqa.selenium.devtools.log.Log; publicclassCaptureConsoleLogs{ privatestaticChromeDriverdriver; privatestaticDevToolschromeDevTools; finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); driver=newChromeDriver(); chromeDevTools=driver.getDevTools(); chromeDevTools.createSession(); chromeDevTools.send(Log.enable()); chromeDevTools.addListener(Log.entryAdded(), logEntry->{ System.out.println("log:"+logEntry.getText()); System.out.println("level:"+logEntry.getLevel()); }); driver.get("https://testersplayground.herokuapp.com/console-5d63b2b2-3822-4a01-8197-acd8aa7e1343.php"); } }
Inourcode,line19usesDevTools::send()toenableconsolelogcapture.
Wethenaddalistenertocaptureallconsolelogsloggedbytheapplication.Foreachlogcapturedbytheapplication,weextractthelogtextusingthegetText()methodandtheloglevelusingthegetLevel()method.
Finally,opentheapplicationandcapturetheconsoleerrorlogpostedbytheapplication.
Captureperformancemetrics
Intoday’sfast-pacedworld,whereweiterativelybuildsoftwareatsuchafastpace,weshouldalsoiterativelydetectperformancebottlenecks.Poorlyperformingwebsitesandpagesthatloadslowlycanleavecustomersdissatisfied.
Canweverifythesemetricsoneverybuild?Yeswecan!
TheCDPcommandtocaptureperformancemetricsisPerformance.enable.Informationaboutthiscommandcanbefoundinthedocumentation.
Let’sseehowthisprocessisdoneinSelenium4andChromeDevToolsAPI.
packagecom.devtools; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importorg.openqa.selenium.devtools.performance.Performance; importorg.openqa.selenium.devtools.performance.model.Metric; importjava.util.Arrays; importjava.util.List; importjava.util.stream.Collectors; publicclassGetMetrics{ finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); ChromeDriverdriver=newChromeDriver(); DevToolsdevTools=driver.getDevTools(); devTools.createSession(); devTools.send(Performance.enable()); driver.get("https://www.google.org"); List<Metric>metrics=devTools.send(Performance.getMetrics()); List<String>metricNames=metrics.stream() .map(o->o.getName()) .collect(Collectors.toList()); devTools.send(Performance.disable()); List<String>metricsToCheck=Arrays.asList( "Timestamp","Documents","Frames","JSEventListeners", "LayoutObjects","MediaKeySessions","Nodes", "Resources","DomContentLoaded","NavigationStart"); metricsToCheck.forEach(metric->System.out.println(metric+ "is:"+metrics.get(metricNames.indexOf(metric)).getValue())); } }
First,wecreateasessionbycallingthecreateSession()methodofDevTools,asshowninline19.
Next,weenableDevToolstocaptureperformancemetricsbysendingthePerformance.enable()commandtosend()asshownonline20.
Onceperformancecaptureisenabled,wecanopentheapplicationandsendthePerformance.getMetrics()commandtosend().ThiswillreturnalistofMetricobjects,whichwecanstreamtogetthenamesofallcapturedmetrics,asshownonline25.
WethendisabletheperformancecapturebysendingthePerformance.disable()commandtosend()asshownonline29.
Toviewthemetricsweareinterestedin,wedefinealistcalledmetricsToCheckandthenprintthevaluesofthemetricsbyloopingoverthelist.
BasicAuthentication
InSelenium,itisnotpossibletointeractwithabrowserpopupbecauseitcanonlyinteractwithDOMelements.Thisposesachallengeforpop-upssuchasauthenticationdialogs.
WecanbypassthisissuebyusingtheCDPAPItohandleauthenticationdirectlywithDevTools.TheCDPcommandthatsetsadditionalheadersforarequestisNetwork.setExtraHTTPHeaders.
Here’showtocallthiscommandinSelenium4.
packagecom.devtools; importorg.apache.commons.codec.binary.Base64; importorg.openqa.selenium.By; importorg.openqa.selenium.chrome.ChromeDriver; importorg.openqa.selenium.devtools.DevTools; importorg.openqa.selenium.devtools.network.Network; importorg.openqa.selenium.devtools.network.model.Headers; importjava.util.HashMap; importjava.util.Map; importjava.util.Optional; publicclassSetAuthHeader{ privatestaticfinalStringUSERNAME="guest"; privatestaticfinalStringPASSWORD="guest"; finalstaticStringPROJECT_PATH=System.getProperty("user.dir"); publicstaticvoidmain(String[]args){ System.setProperty("webdriver.chrome.driver",PROJECT_PATH+"/src/main/resources/chromedriver"); ChromeDriverdriver=newChromeDriver(); //CreateDevToolssessionandenableNetwork DevToolschromeDevTools=driver.getDevTools(); chromeDevTools.createSession(); chromeDevTools.send(Network.enable(Optional.empty(),Optional.empty(),Optional.empty())); //Openwebsite driver.get("https://jigsaw.w3.org/HTTP/"); //Sendauthorizationheader Map<String,Object>headers=newHashMap<>(); StringbasicAuth="Basic"+newString(newBase64().encode(String.format("%s:%s",USERNAME,PASSWORD).getBytes())); headers.put("Authorization",basicAuth); chromeDevTools.send(Network.setExtraHTTPHeaders(newHeaders(headers))); //Clickauthenticationtest-thisnormallyinvokesabrowserpopupifunauthenticated driver.findElement(By.linkText("BasicAuthenticationtest")).click(); StringloginSuccessMsg=driver.findElement(By.tagName("html")).getText(); if(loginSuccessMsg.contains("Yourbrowsermadeit!")){ System.out.println("Loginsuccessful"); }else{ System.out.println("Loginfailed"); } driver.quit(); } }
WefirstcreateasessionusingtheDevToolsobjectandenableNetwork.Thisisshowninlines25-26.
Next,weopenourwebsiteandcreatetheauthenticationheaderstosend.
Online35,wesendthesetExtraHTTPHeaderscommandtosend(),alongwiththeheader’sdata.Thispartwillauthenticateusandallowustobypassbrowserpop-ups.
Totestthisfeature,weclickedontheBasicAuthenticationtestlink.Ifyoutrythismanually,youwillseeabrowserpop-upaskingyoutologin.Butsincewesenttheauthenticationheader,thispopupwillnotappearinourscript.
Instead,wereceivethemessage”Yourbrowserloginwassuccessful!”.
Summary
SeleniumhasbecomeevenmorepowerfulwiththeadditionoftheCDPAPI.WecannowenhanceourteststocaptureHTTPnetworktraffic,collectperformancemetrics,handleauthentication,andsimulategeolocation,timezone,anddevicemode.AndanyotherfeaturesthatmightcomeinChromeDevTools!
Finally,Iwouldliketothankeveryonewhoreadmyarticlecarefully.Reciprocityisalwaysnecessary.Althoughitisnotaveryvaluablething,ifyoucanuseit,youcantakeitdirectly:
Thisinformationshouldbethemostcomprehensiveandcompletepreparationwarehousefor[softwaretesting]friends.Thiswarehousehasalsoaccompaniedtensofthousandsoftestengineersthroughthemostdifficultjourney.Ihopeitcanalsohelpyou!