Selenium4のCDPを使っておしゃれにデバイスエミュレートしたい
みなさんこんにちは、自動テストアーキテクトの森川です。
ついにリリースされましたね、Selenium4正式版!
Announcing Selenium 4 - Selenium
ということで、前回に引き続きCDP(Chrome DevTools Protocol)機能を少し触ってみました。
Selenium4に搭載されたCDPのAPIを叩いてブラウザの位置情報を上書きしてみた|SHIFT Group 技術ブログ|note
デバイスのサイズを変更する
もともとChrome DevToolsにはデバイスのサイズをエミュレートする機能がありまして、CDPでも同じようなことができます。
画面の幅や高さなど、幾つかのパラメータを指定できるようです。
デバイスをコロコロと切り替えることができたらクロスデバイスなテストがずいぶんと捗りそうじゃないですか?
コードはこんな感じでとってもシンプル。
Map deviceMap = new HashMap()
{{
put("width", 360);
put("height", 640);
put("mobile", true);
put("deviceScaleFactor", 100);
}};
driver.executeCdpCommand("Emulation.setDeviceMetricsOverride", deviceMap);
しかしちょっと待てよ。
これでいろいろなデバイスの情報(画面幅とか)を一つ一つ取ってくるのはちょっとしんどいですよね。
調べてみるとpuppeteerではこんなにシンプルに書けるのだとか
const iPhone = puppeteer.devices['iPhone X'];
デバイス名を指定するだけとは、なんておしゃれシンプルな
ぐぬぬ、puppeteerにできてSeleniumにできないなんて悔しい。
ということでやってみました
デバイス情報のjsonを準備
puppeteerから頂いたtsファイルを元にjsonを作って
puppeteer/DeviceDescriptors.ts | puppeteer/puppeteer
■Devices.json
{
"devices":[
{
"name": "Blackberry PlayBook",
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+",
"viewport": {
"width": 600,
"height": 1024,
"deviceScaleFactor": 1,
"isMobile": true,
"hasTouch": true,
"isLandscape": false
}
... デバイスいっぱい
}
]
}
このjsonを読み込むディスクリプターを作って
class DeviceDescriptor {
private Map<String, Object> deviceMap = new HashMap<>();
DeviceDescriptor(String path) throws IOException, ParseException {
generateDeviceMap(path);
}
Map <String,Object> emulateDevice(String name) {
return (Map <String,Object>)deviceMap.get(name);
}
private void generateDeviceMap(String path) throws IOException, ParseException {
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(new FileReader(path));
for (JSONObject device : (Iterable<JSONObject>) (JSONArray) jsonObject.get("devices")) {
Map <String,Object> map = new HashMap<>();
JSONObject vp = (JSONObject)device.get("viewport");
Viewport viewport = new Viewport(
0,
0,
Integer.parseInt(vp.get("width").toString()),
Integer.parseInt(vp.get("height").toString()),
Double.parseDouble(vp.get("deviceScaleFactor").toString())
);
String name = device.get("name").toString();
map.put("name",name);
map.put("userAgent",device.get("userAgent").toString());
map.put("mobile",Boolean.parseBoolean(vp.get("isMobile").toString()));
map.put("viewport",viewport);
deviceMap.put(name, map);
}
}
}
デバイス切り替えのAPIを設けます。
あわせてUserAgentも切り替えてみましょう。
void emulateDevice(String name) {
DeviceDescriptor deviceDescriptor = new DeviceDescriptor(JSON_PATH)
Map <String,Object> device = deviceDescriptor.emulateDevice(name);
Viewport viewport = (Viewport)device.get("viewport");
Map deviceMetrics = new HashMap()
{{
put("width", viewport.getWidth());
put("height", viewport.getHeight());
put("mobile",Boolean.parseBoolean(device.get("mobile").toString()));
put("deviceScaleFactor", viewport.getScale());
}};
((ChromeDriver)driver).executeCdpCommand("Emulation.setDeviceMetricsOverride", deviceMetrics);
devTools.send(Network.setUserAgentOverride(
device.get("userAgent").toString(),
Optional.empty(),
Optional.empty(),
Optional.empty()
));
}
テストコードはこんな感じで、ブラウザでアクセスするのみ。
@BeforeEach
void setup() throws IOException, ParseException {
deviceDescriptor = new DeviceDescriptor(JSON_PATH);
System.setProperty("webdriver.chrome.driver", "C:\\me\\chromedriver.exe");
driver = new ChromeDriver();
wait = new WebDriverWait(driver, Duration.ofSeconds(30));
}
@Test
void switchDeviceDimension() throws IOException, InterruptedException {
driver.get("https://www.jma.go.jp/jma/index.html")
emulateDevice("iPhone X"); // ← これ
wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(BY_MENU_BUTTON));
screenshot(); // サービス関数
}
@AfterEach
void tearDown() {
driver.quit();
}
ほらこれ。どうですか
emulateDevice("iPhone X");
おしゃれシンプルですよね?ね?ね?
実行結果
さてそれでは、実行結果をみてみましょう。
PCとモバイルで切り替わるレスポンシブなサイトといえば気象庁の公式サイトですよね(初耳)
■PCで表示
■iPhone X
モバイル用サイトに切り替わりました。
ブラウザの表示サイズもちゃんと切り替わっていますね。
■iPad Pro landscape
iPadではPCと同じっぽいです。
■BlackBerry Z30
いい感じです。
要素のスクリーンショット
ついでにデバイスサイズを切り替えたときに、要素のスクリーンショットが撮れるか見てみましょう。
Selenium4と要素スクショの過去記事
Seleniumのブラウザ要素スクリーンショットを再考する|SHIFT Group 技術ブログ|note
「キキクル」のメニューボタンを切り取ってみます。
■PC
■iPhone X
モバイル用のメニューボタンになり、ちゃんと切り取られています。
■iPad Pro landscape
PCのままですね。よしよし。
■BlackBerry Z30
ばっちり!
デバイスサイズを切り替えても、要素の切り取りができることがわかりましたね。
違う書き方もあるよ
公式リファレンスではこのようになっています。
Chrome DevTools Protocol - Selenium
devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Emulation.setDeviceMetricsOverride(
(Integer)viewport.getWidth(),
(Integer)viewport.getHeight(),
(Double)viewport.getScale(),
Boolean.parseBoolean(device.get("mobile").toString()),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty()));
この書き方でデバイス切り替えは動作しましたが、要素スクリーンショットは撮れませんでした。なぜでしょうね。時間があれば調べてみましょう。
まとめ
気になる実行時間はこんな感じです。
スクリーンショットを取らなければ各ケースともに1s以内には収まりました。
デバイスサイズをグリグリと変える自動テストに適用できそうです。
・参考:Chrome DevTools Protocol - Emulation domain
・参考:Selenium4: A Peek into Chrome DevTools - by Deepak Attri - CodeX - Medium
・参考:pyppeteerでデバイスをエミュレートする - j3iiifn’s blog
__________________________________
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/