2009-09-29 selenium ide のclickバグの再現方法と対策

selenium ideが clickAndWaitをclickと勘違いしてしまう問題で再現が100%になったから書くよ。
再現方法は簡単。一度登録してすべて選択delete してからもう一度テストを記録すると必ず100%発生するよ。
環境 Selenium IDE 1.0.2 + Firefox 3.5.3 + WindowsXP

この前使ったテストページを表示。
http://rtilabs.net/files/2009_09_02/a.html

イベントを記録する


記録したイベントをCtrl+Aで全選択してDELETE


もう一度記録すると、clickAndWaitがclickになってしまう。


原因。

変数管理が間違っているから。
追加場所を管理する変数が一つずれた値をさすことによって補正ルーチンがエラーになってしまうため、値の補正が行われないため。

とりあえず、ここを直すとバグはなくなる。
でも、副作用はあるよ!
selenium-ide\content\editor.js

Editor.prototype.addCommand 関数(そめっど)

// this.lastCommandIndex = this.view.getRecordIndex();
this.lastCommandIndex = this.getTestCase().commands.length;

関数丸ごと掲載

Editor.prototype.addCommand = function(command,target,value,window,insertBeforeLastCommand) {
    this.log.debug("addCommand: command=" + command + ", target=" + target + ", value=" + value + " window.name=" + window.name);
    if (this.lastWindow) {
        this.log.debug("window.name=" + window.name + ", lastWindow.name=" + this.lastWindow.name);
    } else {
        this.log.debug("window.name=" + window.name);
    }
	if (command != 'open' && 
        command != 'selectWindow' &&
        command != 'selectFrame') {
        if (this.getTestCase().commands.length == 0) {
            var top = this._getTopWindow(window);
            this.log.debug("top=" + top);
            var path = this.getPathAndUpdateBaseURL(top)[0];
            this.addCommand("open", path, '', top);
            this.recordTitle(top);
        }
        if (this.lastWindow != null &&
            !this._isSameWindow(this.lastWindow, window)) {
            if (this._isSameWindow(window.top, this.lastWindow.top)) {
                // frame
                var destPath = this._createPaths(window);
                var srcPath = this._createPaths(this.lastWindow);
                this.log.debug("selectFrame: srcPath.length=" + srcPath.length + ", destPath.length=" + destPath.length);
                var branch = 0;
                var i;
                for (i = 0;; i++) {
                    if (i >= destPath.length || i >= srcPath.length) break;
                    if (destPath[i] == srcPath[i]) {
                        branch = i;
                    }
                }
                this.log.debug("branch=" + branch);
                if (branch == 0 && srcPath.size > 1) {
                    // go to root
                    this.addCommand('selectFrame', 'relative=top', '', window);
                } else {
                    for (i = srcPath.length - 1; i > branch; i--) {
                        this.addCommand('selectFrame', 'relative=up', '', window);
                    }
                }
                for (i = branch + 1; i < destPath.length; i++) {
                    this.addCommand('selectFrame', destPath[i].name, '', window);
                }
            } else {
                // popup
                var windowName = window.name;
                if (window.name == '') {
                    windowName = 'null';
                }
                this.addCommand('selectWindow', "name=" + windowName, '', window);
            }
        }
	}
	//resultBox.inputField.scrollTop = resultBox.inputField.scrollHeight - resultBox.inputField.clientHeight;
    this.clearLastCommand();
	this.lastWindow = window;
    var command = new Command(command, target, value);
    // bind to the href attribute instead of to window.document.location, which
    // is an object reference
    command.lastURL = window.document.location.href;
    
    if (insertBeforeLastCommand && this.view.getRecordIndex() > 0) {
        var index = this.view.getRecordIndex() - 1;
        this.getTestCase().commands.splice(index, 0, command);
        this.view.rowInserted(index);
    } else {
//hacked by rti
//        this.lastCommandIndex = this.view.getRecordIndex();
        this.lastCommandIndex = this.getTestCase().commands.length;	//←ここだよ!!
//-----
        this.getTestCase().commands.splice(this.lastCommandIndex, 0, command);
        this.view.rowInserted(this.lastCommandIndex);
        this.timeoutID = setTimeout("editor.clearLastCommand()", 300);
    }
}

onunload のタイミングで 一番最後に登録されたものが click 系だったら、 AndWait を追加するルーチンがある。
selenium-ide\content\editor.js の Editor.prototype.appendWaitForPageToLoad 関数ね。

だけど、このルーチン、最後のイベントを取得するのに こんな感じでチェックしている。
これはいいんだけど、this.lastCommandIndex ってやつが問題。

Editor.prototype.appendWaitForPageToLoad = function(window) {
    this.log.debug("appendWaitForPageToLoad");
    if (window != this.lastWindow) {
        this.log.debug("window did not match");
        return;
    }
	var lastCommandIndex = this.lastCommandIndex;
	if (lastCommandIndex == null || lastCommandIndex >= this.getTestCase().commands.length) {
		return;				//←ここにひっかかるとバグが発生するよ!!
	}
	this.lastCommandIndex = null;
	var lastCommand = this.getTestCase().commands[lastCommandIndex];
	if (lastCommand.type == 'command' && 
		!lastCommand.command.match(/^(assert|verify|store)/)) {
		if (this.app.getCurrentFormat().getFormatter().remoteControl) {
			this.addCommand("waitForPageToLoad", this.getOptions().timeout, null, this.lastWindow);
		} else {
			lastCommand.command = lastCommand.command + "AndWait";		//ここで補正しているね。ダーティ具合がcool
			this.view.rowUpdated(lastCommandIndex);
		}
	}
    this.clearLastCommand();
	//updateSource();
}


あんまりよくわかっていないんだけど selenium ide では、追加位置を保持するのに2つの方法があるみたい。
1つは、this.view.getRecordIndex() で画面に表示される手いるリストと直結したやつ
もう一つは、this.getTestCase().commands.length で実際のテスト内容を書いているやつ。

で、すべて削除して追加をやると、これが1つずれてしまうみたい。

Editor.prototype.addCommand で、 this.lastCommandIndex に this.view.getRecordIndex() を代入していて、
Editor.prototype.appendWaitForPageToLoadでは、 this.lastCommandIndex を this.getTestCase().commands.lengthで見ている。

それぞれ値の設定、見方の方法が違う。
だから、値のずれが起きたときには、おかしな結果になって判定に失敗しちゃうみたい。
これがおきたとき、テストケースにイベントを追加するのがすっごく遅くなる。他のバグを誘発しているのかもwww

具体的にどこでずれたかまたでは追っていないんだけど、、、
この2つの取得方法で値を取得した結果をprintf デバッグ(笑)で追っかけていくとずれるのがわかるよ。

んで、これを補正してあげるととりあえず大丈夫になる。

これで clickAndWaitが click になることは 100%発生しないはず。
しかーし、このfixには問題点があって、すべてのイベント追加がリストの最下層に追加されてしまうのだ。

selenium ide には、選択位置にイベントを追加してくれる。
これが動かなくなってしまうという諸刃の剣。
つーか、この選択位置に追加機能も最初の追加位置と2個目からの追加位置がずれてしまういう楽しいバグがあるから、そもそもどうなの?っていうかテストツールなのにちゃんとテストしてねーだろう、バグってんだろおぉぉぉぉぉって言いたくなるんだけど、使わせてもらっている立場なのでそんなこといえないから内緒にしておく。(してないぢゃん)

こんな感じで追加する位置がずれます。

選択位置に追加されるはずだが、、、

最初の一見目は選択位置より上に追加されます。

2件目以降は選択範囲の下に追加されます。