logo.png

はじめに

Tampermonkeyは、Chromeをはじめとしたモダンブラウザー上でユーザースクリプトを実行する拡張機能です。

Safariに未対応なものの、オープンソースで開発されているViolentmonkeyというのもあります。

元々は特定のページを使いやすいように書き換えたりする用途に使われますが、開発業務でも入力フォームにテストデータを事前に入力するといった支援ツールとして利用する事ができます。

ここでは、その際によく使ったコードスニペットをまとめてみました。

選択系

// ID属性が設定されている場合はquerySelector()よりgetElementById()を優先して使う
document.getElementById('email').value = email
document.querySelector('input[id="email"]').value = email

// チェックボックスはquerySelectorAll()で処理すると存在しない場合にも対応できる
document.querySelectorAll('input[type="checkbox"][name="agreement"]').forEach(el => {
  el.checked = true
})

// CSSセレクターで特定要素の祖先を検索する場合はclosest()を使う
document.querySelector('button[value="送信する"]').closest('form').classList.add('nodialog')

// CSSセレクターで隣接要素を特定する場合はnextElementSiblingを使う
document.querySelector('label[for="email"]').nextElementSibling.querySelector('input').value = email

// XPathを使う場合は指定文字を含むlegend/label要素の隣接セレクターで特定する
// アスタリスクの部分はdivやspan要素などを指定できるならそちらを使う
document.evaluate('//legend[contains(.,"メールアドレス")]/following-sibling::*//input', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value = email
document.evaluate('//label[contains(.,"メールアドレス")]/following-sibling::*/input', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value = email

// XPathで要素でなく直接値を取りたい場合はstring()とstringValueを使う
document.evaluate('string(//td[.="ID"]/following-sibling::td[contains(.,"詳細")]//a/@href)', document).stringValue
document.evaluate('string(//label[@for="url"]/following-sibling::div//text()])', document).stringValue

// XPathで特定要素の祖先を検索する場合はancestorを使う
document.evaluate('//button[.="送信する"]/ancestor::form', document).iterateNext().classList.add('nodialog')

制御系

// SPAなどページは表示されたが要素がまだレンダリングされていない場合、それを待ってから処理するための関数
function waitForElement(waitFunc) {
  let retry = 0
  return new Promise((resolve, reject) => {
    const interval = setInterval(() => {
      if (waitFunc()) {
        clearInterval(interval)
        resolve()
      } else {
        if (retry++ > 10) {
          console.warn('[waitForElement] waitFunc retry has been exceeded.')
          clearInterval(interval)
          reject()
        }
      }
    }, 500)
  })
}
// 使い方
// waitForElement(() => document.querySelectorAll('input[name="occupation"]').length > 0).then(() => {
//   const occups = document.querySelectorAll('input[name="occupation"]')
//   occups[Math.floor(Math.random() * occups.length)].checked = true
// })

通信系

// fetchして取得したHTMLを解析する場合はDOMParser()オブジェクトを使う
fetch('/users/12345', {credentials: 'include'}).then(r => r.text()).then(t => {
  const d = new DOMParser().parseFromString(t, 'text/html')
  location.href = d.evaluate('string(//td[.="12345"]/preceding-sibling::td[2]//a/@href)', d).stringValue
})

// 別のサーバー(クロスドメイン)に通信する場合はGM_xmlhttprequest()を使う
// メタデータに @grant GM_xmlhttpRequest と通信先URL @connect target.example.com を設定する
GM_xmlhttpRequest({
  url: 'https://target.example.com/users/12345',
  responseType: 'json',
  onload: d => {
    document.getElementById('email').value = d.response.email
  }
})

その他

// unsafeWindowを使うと、対象ページに関数やイベントを追加する事ができる
// メタデータに @grant unsafeWindow を設定する
unsafeWindow.getGlobalIp = () => {
  fetch('https://crossdomain.example.com/books/abcdefg').then(r => r.text()).then(t => {
    console.log(t)
  })
}
// 使い方
// document.body.innerHTML += '<button type="button" onclick="getGlobalIp()">情報を取得</button>'

// 配列からランダムに1つを選ぶ(厳密ではない)
['item1', 'item2', 'item3', 'item4'].sort(() => Math.random() - 0.5).shift()

// ISO-8601(YYYY-MM-DD)形式の日付を得る
new Date().toISOString().slice(0, 10)
new Date().toLocaleDateString('sv-SE')

// 時刻をLeet文字に置換(12:34:56 -> IZEASG)
d.toLocaleTimeString().replaceAll(':', '').split('').reduce((p, c) => p + 'OIZEASGLBP'.substr(c, 1), '')

// 別のページへ値を引き継ぎたい場合はlocalStorageを使う
localStorage.setItem('tm.test.email', email)
localStorage.getItem('tm.test.email')

Tampermonkeyの紹介というより、JavaScriptの紹介みたいになってしまいましたが、参考になれば幸いです。