Puppeteer で指定の Developer Tools 画面を開く

# 目的

`puppeteer` でブラウザアクセスを自動化した上で、[Content-Security-Policy-Report-Only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) レスポンスヘッダが付与された際に `report-to` 先に正しくレポート内容が送付されたかを確認したいことがありました。

その際に、Developer Tools では以下の画面のように開かせたい、という動きを実装します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/050e4485-2862-d408-d72d-dff5900bd774.png)

# Chrome で Developer Tools を開き、表示したい内容に遷移する
例えば、以下のように　Developer Tools　タブやメニューを選択し、表示サイズ変更などを行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/1338984b-332d-b2a2-6b19-e5d7a28d37da.png)

# `Preferences` ファイルを確認する
以下のファイルに先ほど指定した内容が反映されています。

```shell:
cat ~/Library/Application\ Support/Google/Chrome/Default/Preferences | jq '.devtools' 
```

```json:preferences.json
{
  "adb_key": "...",
  "port_forwarding_config": {
    "8080": "localhost:8080"
  },
  "port_forwarding_default_set": true,
  "preferences": {
    "Inspector.drawerSplitViewState": "{\"horizontal\":{\"size\":423.5145129011871,\"showMode\":\"OnlyMain\"}}",
    "InspectorView.screencastSplitViewState": "{\"vertical\":{\"size\":0}}",
    "InspectorView.splitViewState": "{\"vertical\":{\"size\":1041.4018410336284}}",
    "Styles-pane-sidebar-tabOrder": "{\"Styles\":10,\"Computed\":20}",
    "closeableTabs": "{\"security\":true,\"chrome_recorder\":true,\"performance_insights\":true,\"issues-pane\":true,\"release-note\":true}",
    "console.sidebar.width": "{\"vertical\":{\"size\":0,\"showMode\":\"OnlyMain\"}}",
    "console.sidebarSelectedFilter": "\"message\"",
    "consoleShowSettingsToolbar": "false",
    "currentDockState": "\"right\"",
    "dataGrid-networkLog-columnWeights": "{\"name\":17.25083329421154,\"path\":6,\"url\":10.391061452513966,\"method\":6,\"status\":6,\"protocol\":6,\"scheme\":6,\"domain\":6,\"remoteaddress\":10,\"remoteaddress-space\":10,\"type\":6,\"initiator\":10,\"initiator-address-space\":10,\"cookies\":6,\"setcookies\":6,\"size\":6,\"time\":4.358105253274494,\"priority\":6,\"connectionid\":6,\"cache-control\":6,\"connection\":6,\"content-encoding\":6,\"content-length\":6,\"etag\":6,\"keep-alive\":6,\"last-modified\":6,\"server\":6,\"vary\":6,\"waterfall\":6}",
    "drawer-view-selectedTab": "\"console-view\"",
    "drawer-view-tabOrder": "{\"console-view\":10,\"issues-pane\":20,\"release-note\":30}",
    "elements.styles.sidebar.width": "{\"vertical\":{\"size\":0,\"showMode\":\"OnlyMain\"}}",
    "emulation.deviceHeight": "0",
    "emulation.deviceModeValue": "{\"device\":\"\",\"orientation\":\"\",\"mode\":\"\"}",
    "emulation.deviceScale": "1",
    "emulation.deviceScaleFactor": "0",
    "emulation.deviceUA": "\"Mobile\"",
    "emulation.deviceWidth": "400",
    "emulation.showDeviceMode": "false",
    "emulation.showDeviceOutline": "false",
    "emulation.showDeviceScaleFactor": "false",
    "emulation.showRulers": "false",
    "emulation.showUserAgentType": "false",
    "inspectorVersion": "35",
    "messageLevelFilters": "{\"verbose\":true,\"info\":true,\"warning\":true,\"error\":true}",
    "navigator-view-selectedTab": "\"navigator-network\"",
    "networkLogColumns": "{\"name\":{\"visible\":true},\"path\":{\"visible\":false},\"url\":{\"visible\":true},\"method\":{\"visible\":false},\"status\":{\"visible\":true},\"protocol\":{\"visible\":true},\"scheme\":{\"visible\":false},\"domain\":{\"visible\":false},\"remoteaddress\":{\"visible\":false},\"remoteaddress-space\":{\"visible\":false},\"type\":{\"visible\":true},\"initiator\":{\"visible\":true},\"initiator-address-space\":{\"visible\":false},\"cookies\":{\"visible\":false},\"setcookies\":{\"visible\":false},\"size\":{\"visible\":true},\"time\":{\"visible\":true},\"priority\":{\"visible\":false},\"connectionid\":{\"visible\":false},\"cache-control\":{\"visible\":false},\"connection\":{\"visible\":false},\"content-encoding\":{\"visible\":false},\"content-length\":{\"visible\":false},\"etag\":{\"visible\":false},\"keep-alive\":{\"visible\":false},\"last-modified\":{\"visible\":false},\"server\":{\"visible\":false},\"vary\":{\"visible\":false},\"waterfall\":{\"visible\":false}}",
    "networkPanelSidebarState": "{\"vertical\":{\"size\":0,\"showMode\":\"OnlyMain\"}}",
    "networkPanelSplitViewState": "{\"vertical\":{\"size\":165}}",
    "networkPanelSplitViewWaterfall": "{\"vertical\":{\"size\":0}}",
    "networkResourceTypeFilters": "{\"Other\":true}",
    "networkTextFilter": "\"\"",
    "panel-selectedTab": "\"resources\"",
    "panel-tabOrder": "{\"resources\":10,\"console\":20,\"network\":30,\"elements\":40,\"chrome_recorder\":50,\"performance_insights\":60,\"sources\":70,\"timeline\":80,\"heap_profiler\":90,\"security\":100,\"lighthouse\":110,\"chrome-extension://pibckhncbjabhlomohgcdedacopeoomlDr.FLARE\":111}",
    "releaseNoteVersionSeen": "56",
    "request-info-formData-category-expanded": "true",
    "request-info-general-category-expanded": "true",
    "request-info-queryString-category-expanded": "true",
    "request-info-requestHeaders-category-expanded": "true",
    "request-info-requestPayload-category-expanded": "true",
    "request-info-responseHeaders-category-expanded": "true",
    "resourceViewTab": "\"response\"",
    "resourcesLastSelectedElementPath": "[\"reportingApi://\"]",
    "selectedProfileType": "\"HEAP\"",
    "showMediaQueryInspector": "false",
    "sourcesPanelNavigatorSplitViewState": "{\"vertical\":{\"size\":0,\"showMode\":\"Both\"}}",
    "sourcesPanelSplitViewState": "{\"vertical\":{\"size\":0,\"showMode\":\"Both\"},\"horizontal\":{\"size\":0,\"showMode\":\"Both\"}}",
    "standardEmulatedDeviceList": "[{...}]",
    "timelineCountersSplitViewState": "{\"horizontal\":{\"size\":0}}",
    "timelineFlamechartMainAndVitalsView": "{\"horizontal\":{\"size\":0}}",
    "timelineStartCoverage": "false",
    "timelineTreeViewDetailsSplitWidget": "{\"vertical\":{\"size\":0}}",
    "undefined-tabOrder": "{\"Styles\":10,\"Computed\":20,\"sources.scopeChain\":21,\"sources.watch\":22}"
  },
  "synced_preferences_sync_disabled": {
    "adornerSettings": "[{...}]",
    "colorFormat": "\"original\"",
    "emulation.locations": "[{...}]",
    "syncedInspectorVersion": "35"
  }
}
```

# `puppeteer-extra` のプラグインを使う
`puppeteer-extra` の `user-preferences` プラグインを活用します。

- [berstend/puppeteer-extra: 💯 Teach puppeteer new tricks through plugins.](https://github.com/berstend/puppeteer-extra/tree/master)
- [puppeteer-extra/packages/puppeteer-extra-plugin-user-preferences at master · berstend/puppeteer-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-user-preferences)

先ほどの `preferences.json` から使いたい設定値をとってきます。

以下の指定により、目的のの画面内容を指定できます。

- `panel-selectedTab` --> `resources`
- `resourcesLastSelectedElementPath` --> `reportingApi://`

以下のような `puppeteer.js` スクリプトを実行すると

```javascript:puppeteer.js
const puppeteer = require('puppeteer-extra');
const ppUserPrefs = require('puppeteer-extra-plugin-user-preferences');

puppeteer.use(ppUserPrefs({
    userPrefs: {
        devtools: {
            preferences: {
                'InspectorView.splitViewState': JSON.stringify({
                    vertical: { size: 1500 },
                    horizontal: { size: 0 },
                }),
                uiTheme: '"dark"',
                'panel-selectedTab': '"resources"',
                'resourcesLastSelectedElementPath': JSON.stringify([
                    "reportingApi://"
                ])
            },
        },
    },
}));

puppeteer.launch({
    headless: false,
    devtools: true,
    product: 'chrome',
    args: [
        "--short-reporting-delay",
        "--auto-open-devtools-for-tabs"
    ]
}).then(async browser => {
    //const page = (await browser.pages())[0];
    const page = await browser.newPage();
    const response = await page.goto('https://try.cloudflare.com/', {
        waitUntil: "load",
    });
    const headers = response.headers();
    await page.waitForTimeout(5000);
    await page.close();
    await browser.close();
});
```

```shell:
PUPPETEER_PRODUCT=chrome \
PUPPETEER_EXECUTABLE_PATH=/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
node puppeteer.js
```

意図した挙動にできたことが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/b7df5ef5-0cca-40fc-d3f9-df680cddde6d.png)

これで CSP レポーティングのテストがかなりわかりやすくなりました。

# 参考

- [Define default width of Chrome DevTools window - Stack Overflow](https://stackoverflow.com/questions/58473161/define-default-width-of-chrome-devtools-window)
- [Monitor your web application with the Reporting API - Chrome Developers](https://developer.chrome.com/articles/reporting-api/)