はじめに
Seleniumとは
Webブラウザを自動で操作できるツール。
多くのプログラミング言語がサポートされているが、今回はPythonを用いる。
asm15とは
asm15 assembler for IchigoJam / IchigoLatte (本家)
asm15 assembler for IchigoJam / IchigoLatte (筆者による改造版)
IchigoJam向けのマシン語のアセンブルを目的に開発されたアセンブラ。
Webブラウザ上で入出力を行うため、ファイルとの連携は一手間かかる。
※IchigoJamはjig.jpの登録商標です。
今回の目的
Webブラウザ自動操作ツールSeleniumでアセンブラasm15を操作し、
標準入力から受け取ったアセンブリのソースコードをアセンブルして結果を標準出力に出力する。
Seleniumのインストール
今回は、PythonおよびGoogle Chromeが既にインストールされているWindows環境でインストールを行った。
インストール方法および一部の操作方法は、この記事を参考にした。
コマンドプロンプトでPythonのvirtualenv環境を用意し、
以下のコマンドを実行することで、インストールできた。
pip install chromedriver-binary==96.*
pip install selenium
chromedriver-binary==
に続く数字は、記事に従い、
インストールされているGoogle Chromeのバージョンに合わせた。
Google Chromeのバージョンが上がった際のトラブルを避けるため、virtualenvを用いることにした。
Seleniumによるasm15の操作
SeleniumによるWebブラウザの操作は、Pythonのインタラクティブモードで試すことができる。
Webブラウザ (Google Chrome) の起動
以下のプログラムにより、Google Chromeを起動してSeleniumで操作できる状態にできる。
import chromedriver_binary
from selenium import webdriver
driver = webdriver.Chrome()
import chromedriver_binary
はGoogle Chrome起動の準備をしているようで、
これを実行しないとGoogle Chromeの起動に失敗した。
また、--headless
オプションを用いると、Google Chromeのウィンドウを隠した状態で起動できる。
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
Webブラウザの終了
quit
関数により、Seleniumで起動したWebブラウザを終了できる。
driver.quit()
また、with
構文を用いることで、ブロックの実行終了時に自動で終了してくれる。
with webdriver.Chrome(options=options) as driver:
# do work
Working with windows and tabs | Selenium
指定のURLへの遷移
get
関数により、指定のURLに遷移できる。
driver.get("https://ichigojam.github.io/asm15/")
HTML要素のハンドラの取得
find_element
関数により、操作対象のHTML要素を選択し、ハンドラを取得できる。
今回は、要素のid
属性に基づいて選択するBy.ID
を用いる。
from selenium.webdriver.common.by import By
textarea = driver.find_element(By.ID, "textarea1")
Finding web elements | Selenium
該当する要素が存在しない場合、
selenium.common.exceptions.NoSuchElementException
例外が発生するようである。
入力欄への文字列の入力
send_keys
関数により、HTML要素の入力欄に文字列を入力できる。
英語の大文字や小文字、記号、日本語も指定通りに入力できるようである。
textarea.send_keys("' テスト (test)\nRET\n")
clear
関数により、HTML要素の入力欄に入力されている文字を消去できる。
textarea.clear()
入力欄からの文字列の取得
get_attribute
関数によりHTML要素の属性の値を取得でき、
入力欄に入力されている文字列もこれで取得できるようである。
textarea.get_attribute("value")
text
プロパティもあり、執筆時点におけるasm15のアセンブル結果はこれでも取得できたが、
入力した内容は反映されないようだった。
textarea.text
Information about web elements | Selenium
(執筆時点において、属性の取得については書かれていないようである)
SELECT要素の操作
HTMLのSELECT要素の操作は、Select
オブジェクトを用いて行うことができる。
例えば、select_by_visible_text
関数を用いると、表示される文字列を指定して選択できる。
from selenium.webdriver.support.select import Select
selfmt = driver.find_element(By.ID, "selfmt")
selfmt_selobj = Select(selfmt)
selfmt_selobj.select_by_visible_text("bas (hex)")
Working with select list elements | Selenium
ボタンのクリック
click
関数により、HTML要素をクリックできる。
btn = driver.find_element(By.ID, "submit-button")
btn.click()
アラートダイアログへの対処
執筆時点におけるasm15は、アセンブル時のエラーをアラートダイアログで表示する。
アラートダイアログが出ているかをチェックするため、タイムアウトを短く設定したWebDriverWait
を用い、
wait
関数でアラートダイアログが出るまで待たせる。
タイムアウトは、WebDriverWait
の第2引数で、秒単位で設定できるようである。
アラートダイアログが出ていればそのダイアログのハンドラが返り、
出ていなければselenium.common.exceptions.TimeoutException
例外が発生するようである。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 1)
alert = wait.until(EC.alert_is_present())
アラートダイアログが出ていた場合、text
プロパティでアラートダイアログの内容を取得し、
accept
関数でアラートダイアログを閉じることができる。
alert.text
alert.accept()
Waits | Selenium
JavaScript alerts, prompts and confirmations | Selenium
まとめ
今回紹介した要素を組み合わせた以下のプログラムにより、asm15を操作し、
標準入力から受け取ったソースコードをアセンブルして結果を標準出力に出力できる。
また、発生したエラーは標準エラー出力に出力する。
import sys
import chromedriver_binary
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.common.exceptions import TimeoutException
# open Chrome
options = webdriver.ChromeOptions()
options.add_argument("--headless")
with webdriver.Chrome(options=options) as driver:
# open asm15
driver.get("https://ichigojam.github.io/asm15/")
# set output format
selfmt = driver.find_element(By.ID, "selfmt")
selfmt_selobj = Select(selfmt)
selfmt_selobj.select_by_visible_text("bas (hex)")
# set assembly source
asm_source = sys.stdin.read()
textarea_asm = driver.find_element(By.ID, "textarea1")
textarea_asm.clear()
textarea_asm.send_keys(asm_source)
# assemble
assemble_button = driver.find_element(By.ID, "submit-button")
assemble_button.click()
# get errors from alert dialog(s)
wait = WebDriverWait(driver, 1)
while True:
try:
alert = wait.until(EC.alert_is_present())
except TimeoutException:
break
sys.stderr.write(alert.text + "\n")
alert.accept()
# get assembled result
textarea_res = driver.find_element(By.ID, "textarea2")
res = textarea_res.get_attribute("value")
sys.stdout.write(res)
テスト用に以下のソースコードを用意した。
R0 = R0 + 1
RET
R0 = R0 + 1234
RET
実行してみると、システムから余計な出力がされるものの、
アセンブル結果とアセンブル時のエラーを取得できていることがわかる。
1回の実行には7秒くらいかかった。
(ve) YUKI.N>selenium_asm15.py < test.txt > test_out.txt
DevTools listening on ws://127.0.0.1:59356/devtools/browser/6cc184b3-4a60-4d3c-b9a4-e36045d28b75
[0103/170014.011:INFO:CONSOLE(0)] "Error with Permissions-Policy header: Unrecognized feature: 'interest-cohort'.", source: (0)
(ve) YUKI.N>cat test_out.txt
10 poke #700,#40,#1C,#70,#47
(ve) YUKI.N>selenium_asm15.py < test_error.txt > test_error_out.txt
DevTools listening on ws://127.0.0.1:59375/devtools/browser/04e8fb18-1d26-4a91-8764-cfbf4553e7db
[0103/170156.926:INFO:CONSOLE(0)] "Error with Permissions-Policy header: Unrecognized feature: 'interest-cohort'.", source: (0)
asm error in 1
R0 = R0 + 1234
Error: over!
(ve) YUKI.N>cat test_error_out.txt
10 poke #700,#70,#47
(ve) YUKI.N>