見出し画像

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


__________________________________

執筆者プロフィール:森川 知雄
中堅SIerでテスト管理と業務ツール、テスト自動化ツール開発を12年経験。SHIFTでは、GUIテストの自動化ツールRacine(ラシーヌ)の開発を担当。GUIテストに限らず、なんでも自動化することを好むが、さまざま案件で自動化、効率化による顧客への価値創出を日々模索している。2021年からは技術コミュニティSHIFT EVOLVEの運営を担当。

お問合せはお気軽に
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/