Selenium 4.0 이상부터는 브라우저와의 양방향 통신을 위해 Bidirection API를 개발하고 있습니다.
그러나 아직까지 많은 부분에서 부족한 점이 많은데요, 그렇기 때문에 크롬 한정으로 DevTool을 사용할 수 있는 기능을 제공중입니다.
이번 포스트에서는 그 중 몇가지 기능들을 기재해봅니다.
BiDirectional functionality
Selenium is working with browser vendors to create the WebDriver BiDirectional Protocol as a means to provide a stable, cross-browser API that uses the bidirectional functionality useful for both browser automation generally and testing specifically. Before now, users seeking this functionality have had to rely on with all of its frustrations and limitations.The traditional WebDriver model of strict request/response commands will be supplemented with the ability to stream events from the user agent to the controlling software via WebSockets, better matching the evented nature of the browser DOM.
Because it’s a bad idea to tie your tests to a specific version of a specific browser, the Selenium project recommends using WebDriver BiDi wherever possible. However, until the spec is complete there are many useful things that the CDP offers. To help keep your tests independent and portable, Selenium offers some useful helper classes. At the moment, these use the CDP, but when we shall be using WebDriver Bidi as soon as possible
https://www.selenium.dev/documentation/webdriver/bidirectional/
Selenium에서는 W3C 규약을 준수하며 브라우저 개발업체들과 협력해나가며 양방향 통신 기능을 개발중이지만, 아직은 많이 미숙한 상황이다.
그렇지만 양방향 통신기능이 필요한 많은 개발자들도 있기 때문에, 이 개발자들은 필요하다면 아직 불완전하고 브라우저의 버전에 의존적이긴 하지만, 크롬개발자도구 프로토콜을 이용해라~
라는 것 같습니다.
https://www.selenium.dev/documentation/webdriver/bidirectional/chrome_devtools/
DevTools devTools = driver.getDevTools();
devTools.createSession();
Chrome DevTool Protocol(a.k.a, CDP) 를 이용하기 위해서는 일단, 자신이 원하는 url로 이동하기 전에, 세션을 만들어주어야합니다.
인증서가 유효하지 않은 사이트에 접속하려면, 유효하지 않은 사이트로 노출됩니다.
이 사이트로 이동하기 위해서는 고급
버튼을 눌러 하단의 링크를 통해 이동해야하는데요, 이것을 무시하는 옵션을 설정할 수 있습니다.
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Security.enable());
devTools.send(Security.setIgnoreCertificateErrors(true));
driver.get("https://expired.badssl.com");
send()
에 Security
클래스의 메소드를 넘겨주면서 설정할 수 있습니다.
크롬에서의 위치정보를 변경할 수 있습니다.
public static void main(String[] args) {
ChromeDriver driver = DriverFactory.getDriver();
DevTools devTools = DriverFactory.createDevTools();
devTools.createSession();
devTools.send(Emulation.setGeolocationOverride(
Optional.of((Number) 51.509865),
Optional.of((Number)(double)-0.118092),
Optional.of((Number) 100)
)
);
driver.get("https://mycurrentlocation.net/");
driver.quit();
driver.close();
}
setGeolocationOverride
의 파라미터로는, 경도, 위도, 정확도 순입니다.
이후에 mycurrentlocation.net
으로 들어가면 크롬으로 접속한 자신의 위치를 확인할 수 있습니다. 이곳으로 접속해서 위치정보가 잘 변경되어있는지 확인할 수 있습니다.
Emulation.setDeviceMetricsOverride()
를 이용해서 크롬에서 디바이스 해상도를 조절할 수 있습니다. 이것을 통해 모바일웹의 UI를 테스트해볼 수도 있을 것 입니다.
이렇게 하나의 맵의 형태로 만들 수는 있겠지만... 메소드의 파라미터 형태로 하는게 훨씬 쉬울 것 같습니다.
특정 네트워크 리퀘스트들을 막아볼 수 있습니다.
public class TestBlockingNetworkRequests {
public static void main(String[] args) {
ChromeDriver driver = DriverFactory.getDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Network.enable(Optional.empty(),
Optional.empty(),
Optional.empty()));
devTools.send(Network.setBlockedURLs(ImmutableList.of("*.jpg", "*.png", "*.jpeg")));
driver.get("http://makemytrip.com");
driver.quit();
driver.close();
}
}
일단 Network.enable()
을 한 다음, Network.setBlockedURLs
를 이용해 블록킹할 정보들을 전달합니다.
위 예제의 코드에서는 ImmmutableList
를 이용해서 리퀘스트 끝에 그림확장자들이 포함되어있는 리퀘스트들을 차단했습니다.
테스트할 때 그림이나 영상을 차단한다면 오히려 페이지의 로딩시간을 줄일 수 있어서 element들을 체크하는 테스트라면 테스트 수행시간을 단축시킬 수 있을 것 입니다.
public class TestNetworkSpeed {
public static void main(String[] args) {
ChromeDriver driver = DriverFactory.getDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
//setting network tracking
devTools.send(Network.enable(
Optional.empty(),
Optional.empty(),
Optional.empty()
));
devTools.send(Network.emulateNetworkConditions(
false, //offline
100, // latency
2000, //downloadThroughPut
1000, //UploadThroughPut
Optional.of(ConnectionType.CELLULAR3G)
));
driver.get("http://way2automation.com");
driver.close();
driver.quit();
}
}
Network.emulateNetworkConditions
을 이용하여 속도조절을 할 수 있습니다.
여기서 ConnectionType
을 통해 2G, 3G, 4G 등의 설정이 가능합니다.
public class TestOverrideTimeZone {
public static void main(String[] args) throws InterruptedException {
ChromeDriver driver = DriverFactory.getDriver();
driver.manage().window().maximize();
driver.get("https://whatismytimezone.com/");
Thread.sleep(5000);
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Emulation.setTimezoneOverride("EST"));
driver.get("https://whatismytimezone.com/");
Thread.sleep(5000);
driver.close();
driver.quit();
}
}
Emulation.setTimezoneOverride()
를 이용해 Standard 타임존을 변경할 수 있습니다.
public class TestConsoleLogs {
public static void main(String[] args) throws InterruptedException {
ChromeDriver driver = DriverFactory.getDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Log.enable());
devTools.send(Console.enable());
devTools.addListener(Log.entryAdded(), entry -> {
System.out.println(entry.getTimestamp());
System.out.println(entry.getLevel());
System.out.println(entry.getText());
});
devTools.addListener(Console.messageAdded(), message -> {
System.out.println(message.getLevel());
System.out.println(message.getText());
});
driver.get("http://flipkart.com");
((JavascriptExecutor) driver).executeScript("console.log('This is a sample log')");
Thread.sleep(5000);
driver.close();
driver.quit();
}
}
addListener
를 통해 log와 console 설정을 해줍니다.
위 예제에서는 리스너 내부에서 프린트문을 작성하여, 콘솔에 무엇인가 발생하면 프린트하도록 했습니다.
이후 JavascriptExecutor
를 이용해 콘솔로그를 실행해줍니다.
이렇게 하면 실행한 자바스크립터로 실행된 콘솔로그가 로그에 찍히는 것을 확인할 수 있습니다.
네트워크의 리퀘스트와 리스폰스를 확인할 수 있습니다.
public class TestRequestAndResponseHeader {
public static void main(String[] args) throws InterruptedException {
ChromeDriver driver = DriverFactory.getDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty()));
driver.get("https://www.google.com");
devTools.addListener(Network.requestWillBeSent(), requestWillBeSent -> {
Headers header = requestWillBeSent.getRequest().getHeaders();
if (!header.isEmpty()) {
System.out.println("Request headers : ");
header.forEach((key, value) -> {
System.out.println(" " + key+ " = " + value);
});
}
});
System.out.println("---------------------");
devTools.addListener(Network.responseReceived(), responseReceived -> {
Headers header = responseReceived.getResponse().getHeaders();
if (!header.isEmpty()) {
System.out.println("Response headers : ");
header.forEach((key, value) -> {
System.out.println(" " + key+ " = " + value);
});
}
});
Thread.sleep(5000);
driver.close();
driver.quit();
}
}
리퀘스트와 리스폰스 각각 아래의 메소드를 리스너에 설정해서 헤더정보를 확인할 수 있습니다.
Network.requestWillBeSent
Network.responseReceived
리스폰스의 정보와 상태코드를 확인할 수 있습니다.
System.out.println("Response URL is : "+ responseReceived.getResponse().getUrl());
System.out.println("Status code : " + responseReceived.getResponse().getStatus());
헤더값을 임의로 변경하거나 조작할 수 있습니다.
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("customHeaderName", "customHeaderValue");
headers.put("Dahun", "Automation Tester");
Headers head = new Headers(headers);
devTools.send(Network.setExtraHTTPHeaders(head));
맵을이용해 Key, Value형태로 넣어서 사용할 수 있습니다.
user agent의 값을 변경할 수 있습니다.
사용자 에이전트 (User Agent)란, 우리가 사용하는 웹 브라우저 속에 숨겨진 중요한 기능 중 하나를 말합니다. 간단히 말해 내가 어떤 OS를 쓰고 있고, 버전은 어떤 버전인지 웹 브라우저의 정보는 어떤 것인지 등을 담고 있는 번호판 같은 개념입니다.
String userAgent = "Mozilla/5.0 (Macintoshl Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like, Gecko) Chrome/96.0.4664.55 Safari/537.36";
devTools.send(Network.setUserAgentOverride(
userAgent,
Optional.empty(),
Optional.empty(),
Optional.empty()
));
driver.get("https://www.whatismybrowser.com/detect/what-is-my-user-agent");
Network.setUserAgentOverride
를 이용해서 변경 가능합니다.
크롬 개발자도구의 퍼포먼스 탭의 기능입니다. 다만 좀 더 상세한 기능까지는 어려웠고, 간단히 네트워킹할 떄의 퍼포먼스를 조회해볼 수 있었습니다.
devTools.send(Performance.enable(Optional.of(
Performance.EnableTimeDomain.TIMETICKS
)));
driver.get("https://www.google.com");
List<Metric> metrics = devTools.send(Performance.getMetrics());
metrics.forEach(metric -> {
System.out.println(metric.getName() + " : " + metric.getValue());
});
ref.