stable or unstable, that is the question

wasavi を Chrome、Opera、Firefox と各エクステンションの公式リポジトリへ登録してある。しかし、それと同時に、ここのサーバでもエクステンションのパッケージを野良 build として置いてある。

この 2 つのパッケージの関係は、ありがちなことに、公式 build が安定版、野良 build が最新だが安定性は保証しない版ということになる。

さて野良 build は今まで週一で更新してきたのだが、最近はさぼり気味だ。なぜか。現在 Firefox 版の wasavi は full review 待ちという状態である。full review がなされたとして、そのまますんなり通るとは考えていない。いろいろと指摘をされるだろう。そうすると当然修正するわけだが、そのときに関係ない機能追加とかがあると向こうのレビュアーを混乱させるだけである。なので、大掛かりなコードの改変はしにくいのだ。

もちろん wasavi はローカルでは Subversion で管理しているので、Firefox 公開版と trunk は別に管理することもできるが……実際面倒だ。

all tests are migrated to java

いろいろ近況。

  • 500 項目すべてのテストを java へ移行した。クリップボードアクセスなどコマンドが非同期で実行されるものもテストできるようになった。
  • ex コマンド入力時のショートカットを拡充した:
    • ^A, HOME: 行先頭へ
    • ^E, END: 行末尾へ
    • ^B, LEFT: 1文字左へ
    • ^F, RIGHT: 1文字右へ
    • ^H: カーソルの左の1文字削除
    • ^N, DOWN: 履歴を1つ進める
    • ^P, UP: 履歴を1つ戻る
    • ^U: 全文字削除
    • ^V: コントロールコード入力のためのプリフィクス
    • ^W: カーソルの左の1単語削除
    • DELETE: カーソルの右の1文字削除

    chromeでは一部のショートカットをスクリプトで制御できない。それには ^N や ^W も含まれる。

  • Firefox の full review は Queue Position: 46 of 113。
  • wasavi 終了時に、マークを寄生先 textarea へ保存するようにした。
  • 寄生先 textarea のサイズ変更にある程度追従するようにした。

watch a test

ググればたくさん情報は得られるが、とりあえずメモ。

junit でテストを書く際、今何のテストをしているのか……などのログを吐きたいことがある。例えば wasavi のテストで、何らかのミスがあって、WebDriverWait がタイムアウトするとする。タイムアウト自体は catch 節で受け取るので、そこで単に System.out.println(“timed out!”); すれば ant が保存するログに含まれる。が、どのテストで発生したかまではわからない。

そこで、テストケースで

@Test public void foo () {
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName());
}

などと書けばログにテスト中のメソッド名が書かれるので判別する材料になる。しかしこれはすべてのテストケースに埋め込まなければならないので、とても煩雑だ。

さて junit4 には TestWatcher というものが用意されていて、テスト開始・終了、あるいは成功時・失敗時に任意の処理をはさむことができる。開始・終了というのは @Before と @After と被っているが、これらはあくまでテスト視点での開始・終了なのに対して、TestWatcher のそれはテストランナーから見た、「テストメソッド」の開始・終了であり、テストメソッドの素性などのメタな情報を利用することができる。


import org.junit.*;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class WasaviTest {
protected String logText;

@Rule public TestRule watcher = new TestWatcher() {
protected void starting (Description d) {
System.out.println("Testcase: " + d.getMethodName());
}
protected void failed (Throwable e, Description d) {
System.out.println(d.getMethodName() + " FAILED\n" + logText);
}
};

@After
public void tearDown () {
logText = driver.findElement(By.id("test-log")).getAttribute("value");
}
}

こんな感じで自前のテストの基底クラスに仕込んでおけば、これを継承した個々のテストでは何も考えることなく勝手にテストケースの名前が出力される。

くわしくは javadoc を見れば全部書いてある。

buffering key input

キー入力周りを書き直した。

wasavi は document に対して keydown、keypress 両方をリスンする。keydown では機能キー(esc、tab、backspace、enter、pageup、pagedown、home、end、insert、delete、F1 ~ F12 の入力を見る。keypress では文字を表すキーを見る。

機能キーは ctrl および shift 併用の入力は、併用しない入力とは別個のキー入力として扱う(が、:map に定義する展開パターンではまだモディファイアキーに対応してない)。一方文字キーは、特定の キーと同時に ctrl が押されていたらコントロールコードの入力とみなすほかは、ctrl も shift も無視する……これは、特に shift 併用した際にキーボードレイアウトがらみで判断が難しいからだ。難しいというより、無理だと思う。あくまで入力された文字そのものだけをみてどのコマンドにディスパッチするかを決める。

また、スムーズスクロール中のキー入力を今までは単に捨てていたのを、キューにバッファするようにした。スクロールが完了した直後にキューをすべて掃き出す。これによりテストがちょっと楽になる。

 * * *

ところで amo を覗いてみたら、wasavi の full review は Queue Position: 45 of 106 だった。おーけっこう進んでるー!

requested full review

Firefox の Add on は preliminary review と full review があって、前者はセキュリティに関する点など最低限の部分的なレビューをした後、限定的に公開される。preliminary review に通ってから 10 日ほどの待機期間みたいなのがあって、それが過ぎると full review を申請できるようになる。

というわけで、full review を申請した。

それにしても 8 月 17 日に preliminary review 申請、29 日に通過、9 月 6 日に full review 受付……ってなかなかのんびりしたペースだなあ。そして、full review 待ちのキューには現在 132 個の Add on が溜まっている(wasavi の位置は 133 of 134)。full review ひとつに付き 10 日くらいかかる。

何人がかりで full review してるのかはわからないが、いつ順番来るのこれ?

asynchronous madness

Selenium でテストすると keypress イベントが発生したりしなかったりする件。

原因は wasavi 側にあった。G とはつまり指定の行へカーソルを移動させる vi コマンドだ。このとき、スクロールが発生するのだが(実際のビューのスクロール量が 0 であっても)、[cci]:set smooth[/cci] の状態だと、スクロールは非同期に行われる。すっかり忘れていた。スクロールが完了するまで、keydown 時に preventDefault() される。つまり、keypress は発生しない。

そして、G に後続するテスト用コマンドはまさにスクロールが完了するまでの狭間のタイミングに送信されていたのであった。こりゃーちゃんとテストできるわけないよ。

というわけで、G を送信した後はコマンドの実行が完了するまで待つことにして解決。

で、とりあえず、jsunit 版の基本編集のテスト editing.js の 51 テストを移植したのだけど。javascript のコードを java に移すというのは、メロンパンの中をくりぬいてメロンを詰め込むようななんともいえない気持ちになりますな。

to open a hole #5

keypress が正しく生成されない件。

Wasavi.send("1G1|y3lp");

といったコードでテストすると、”G” 以降のストロークについて keydown イベントは発生するものの、keypress が一切発生しなくなる。chromedriver_win_23.0.1240.0 で確認した。

これを、

Wasavi.send("gg1|y3lp");

にすると、期待通りのイベントが発生する。となるとやはり “G” が悪いのか。”G” に付随して、shift キーのプレスとリリースもエミュレートされるのだが、この辺が怪しい……? しかし興味深いことに、”|” も shift キー押下イベントが生成されるのだが、それによって keypress が脱落することはない。そうすると関係ないか。

issues に似たバグがないこともないのだけど、解決している様子はない。

実際にストロークを送信する部分は

private void sendStrokes (CharSequence[] strokes) {
Actions act = new Actions(driver);
for (CharSequence s: strokes) {
act.sendKeys(s);
}
act.perform();
}

という感じ。もしかしてこの辺に問題があるのだろうか。もしかしたらまとめてじゃなく細切れで perform() すれば sendKeys() の内部状態(があるとして)がそのたび初期化されてうまく行くかも:

for (CharSequence s: strokes) {
for (int i = 0, goal = s.length(); i < goal; i++) { Actions act = new Actions(driver); act.sendKeys(s.subSequence(i, i + 1)); act.perform(); } }

結果: 変わらず。Selenium 内の問題ではなく chromedriver.exe の中の問題なのか?

ちなみに Opera や Firefox ではどうなのかというと、Opera では textarea に対する sendKeys() 自体が変。"hello!" とかは自動入力できるのだが、Keys.CONTROL とか Keys.INSERT とかは無視される。つまり wasavi を起動させられない。Firefox は textarea に対しては sendKeys() は上手く動いて、wasavi を起動させられるのだが、起動した wasavi の iframe に対して sendKeys() が行われない。つまり wasavi を操作できない。

というわけで、結局まともにブラウザを操作できるのが、(うちの環境では)Chrome だけなのだ。うーん動かし方が悪いのか……?

とにかく Chrome が最後の砦なので、何とか正確にキーストロークを生成してもらわないと困る。

to open a hole #4

Selenium からブラウザに対して任意の javascript を実行させるのに、executeScript() が用意されている。このメソッドは、引数で指定された javascritpt コード をブラウザ上で匿名関数の中身として実行する。

javascript コードが、”return なんちゃら;” であった場合はどうなるのか? その点も抜かりはない、ということになっている。
http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html
返された何者かの型によって、よきに計らってくれる。

ところでブラウザ上で実行される javascript が返す何かが JSON を文字列化した文字列であった場合。なんか一切文書化されていないが(ChromeDriver 特有の動作なのかもしれない)、自動的に Map ……というか google collections library の TransformedEntiresMap に変換されるような感じがする。

これはなかなかに余計なお世話なのではないか。wasavi の状態を返すのに、それを JSON 化した文字列を返している。文字列を返したのなら、java 側でも文字列として現れてほしい。Selenium 側ではやはりそれを JSON オブジェクト化した上で扱うつもりでコードを書いたら、まったくまるで動かないので悩んでしまったぞ。

しかたがないので、ブラウザ側の javascript を経由するのではなく直接 wasavi を保持する iframe の 属性を java 側から読むようにした。直接読むと、:quit 近辺のテストあたりが影響を受ける(状態を取得しようとした時点ですでに wasavi は消滅している)のでなかなか困るのだが。

to open a hole #3

Selenum は、実際に人がブラウジングする過程をなぞるのでブラウザの実動作を対象にテストできるというのがウリなのだと思うけど、ちょっと不思議な動作がある。

起動した wasavi に対してキーストロークを送る。

public void testPasteCharsForward () {
Wasavi.send("i", "foobar\nfoobar", Keys.ESCAPE);

Wasavi.send("1G1|y3lp", Keys.ESCAPE);
assertEquals("ffoooobar\nfoobar", Wasavi.getValue());
}

これを Chrome に対して動かす。Chrome 版 wasavi ではキー入力イベントは keydown と keypress 両方処理している。文字を示すキーは keypress で、機能を示すキーは keydown で扱う。

ところで実際に実行してみると 2 つめの Wasavi.send() で

keydown 27 (U+001B)
keydown 80 P (U+0050)
keydown 76 L (U+004C)
keydown 51 3 (U+0033)
keydown 89 Y (U+0059)
keydown 220 (U+00DC)
keydown 16 (Shift)
keydown 49 1 (U+0031)
keypress 71 G (U+0000)
keydown 71 G (U+0047)
keydown 16 (Shift)
keypress 49 1 (U+0000)
keydown 49 1 (U+0031)

こんな感じのキー入力がなされる(行はイベント種別、キーコード、対応する文字(あれば)、keyIdentifier の値。いちばん上が最新)。これ、変だ。”G” の入力以降、文字を示すキーに対応する keypress が発生していない。したがってテストも失敗する。

これは一体?