Firefox で selenium を実行しながら JavaScript を on/off する方法

# selenium 実行中に about:config の状態を変更する
## 環境
Windows 10 Pro
Firefox 108.0.2 64bit
Python 3.9.13 (Anaconda)
selenium 4.7.2

あるいは
Python 3.7.15
selenium 3.141.0

## 概要
selenium の実行中に about:config を開いて javascript.enabled の値を変更する。

具体的には、次のようなコードを実行する。
```Python
from selenium import webdriver
from selenium.webdriver.common.by import By

options = Options()
options.binary_location = r'C:/Program Files/Mozilla Firefox/firefox.exe'
browser = webdriver.Firefox(options=options)
browser.get('about:config')
sleep(1)

browser.find_element(By.ID, 'warningButton').click()
sleep(1)

textbox = browser.find_element(By.ID, 'about-config-search')
textbox.send_keys('javascript.enabled')
sleep(1)

browser.find_element(By.XPATH, '/html/body/table/tr/td[2]/button').click()
sleep(1)
```
実行ごとに、enable / disable が切り替わる。特に指定してなかったらデフォルトは enable なので、この処理で disable になる。

この一連の処理が終わった時点の画面は about:config のままなので、この後、必要なページに遷移する。
## 背景
selenium で Firefox の about:config で指定するような細かい preference をコントロールしたい場合、selenium.webdriver.firefox.options.Options
 クラスを使えばいい。

例えば javascript を disable にしたいのなら、次のように引数に option を指定して WebDriver を構築する。

```Python
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.preferences.update({
    "javascript.enabled": False,
})

browser = webdriver.Firefox(options=options)
```
これで javascript を disable にした状態のブラウザを立ち上げることはできるのだが、このオプションを実行中で切り替える方法が分からない。

options を変更しただけでは状況は変わらない。webdriver を取得し直すと、それまでクロールした環境のログイン情報等が初期化されて消えてしまう。何か update するメソッドがありそうなものだが、よく分からない。真面目に探せば正しい方法があるのかもしれない。

何で実行中でそんなことをするのかというと、基本、javascript は disable で使っているのだが、javascript が disable だとログインできないようなサイトがあるので、その時だけ enable にしたいのだ。

## Solution

いろいろ調べているうちに面倒になってしまったので、発想を変えて、about:config の画面を開いて javascript.enabled のところをクリックしてしまえばいいのでは、と思いついた(笑)。試しにやってみたら上手くいった。selenium なんだから、普段ブラウザでしているのと同じようにやればいいわけだ。

こんなアホなソリューションはまだ見たことがないような気がするから投稿することにした。いや、何か大昔にこういうことをやったような気もするが、よく覚えてない。

javascript と同様に、実行中によく切り替えたくなるのが画像の block である。これはちょっと面倒なのが、permissions.default.image という文字列を丸ごと含んだ、services.sync.prefs.sync.permissions.default.image という名前の preference があるのだ。検索のボックスに permissions.default.image を指定したら2行出てくるので、操作する行を選択する必要がある。

試行錯誤して現状で動くようにしたのが次のコード。Firefox の configuration が将来変更になったときに、このままでは動作しないかもしれない。その種の事故を避けたければ th に対応するテキストを比較して判断して…という真面目な処理を書けばいいのだが、そこまでする義理はないというか、業務用のコードではなくプライベートな趣味のレベルのものなので、そこまで労力をかける必要はないと判断し、杜撰にやっている。

```Python
'''images
1 Allow all images
2 Block all images
3 Prevent third-party images
'''
textbox = browser.find_element(By.ID, 'about-config-search')
textbox.send_keys('permissions.default.image')
sleep(1)

elems = browser.find_elements(By.XPATH, '//*[@id="prefs"]')
tr = elems[0].find_elements(By.TAG_NAME, 'tr')
sleep(1)

td = tr[1].find_elements(By.TAG_NAME, 'td')
sleep(1)

td[1].find_element(By.TAG_NAME, 'button').click()
sleep(1)

number = tr[1].find_element(By.TAG_NAME, 'input')
# 3 to prevent third-party images
number.send_keys('3')
sleep(1)

td[1].find_element(By.TAG_NAME, 'button').click()
sleep(1)
```

## ハマりポイント

### textbox
javascript off と image off を連続してやるときに、textbox は clear してから send_keys を呼ばないと、今ある文字列の後に追加してしまう。

### sleep
sleep() の秒数は適当に指定している。sleep がないと DOM が更新される前に探しに行ってしまうため動作しない。sleep(1) で動いているけど、多分もっと短くても動く。厳密にやりたければ active になるのを待ってから action というのが定番だが、そこまでやる義理は…。

### options.binary_location
よく分からないが、最近、Firefox の webdriver を取得するときに、options.binary_location を指定しないとドコニアルカワカラン系のエラーが出るようになった。こちらの環境では、次のように指定して解決している。
```Python
options.binary_location = r'C:/Program Files/Mozilla Firefox/firefox.exe'
```
別の場所にインストールしている場合は、環境に合わせて指定しないと動かないはず。
### deprecated
selenium の find_element 系の deprecated な書き方が最近使えなくなった。昔の書き方だとこんな感じになる。
```Python
browser.find_element_by_id('warningButton').click()
sleep(1)

textbox = browser.find_element_by_id('about-config-search')
textbox.send_keys('javascript.enabled')
sleep(1)

browser.find_element_by_xpath('/html/body/table/tr/td[2]/button').click()
sleep(1)
```