サイトマップ

XmlDriver - WebDriverをXMLから呼び出す

Selenium 2.XをJavaScriptで使う』でJavaScriptでもWebDriverのコーディングができることがわかりましたが、どうも釈然としません。その原因の1つはドキュメントとの親和性でした。テストの実施前に記述する仕様書、テストの結果を示す報告書が必要です。プログラミング言語はこういったものと連携するのが上手ではありませんが、XMLならばなんとかなりそうな気がします。

そこで、XMLで定義したテストケースでWebDriverを実行するXmlDriverなるものを考えてみました。Driverの名前につけるのは、本来ならばブラウザの名前なのでXmlDriverという言い方は間違っているかもしれませんが、プロトタイプだからあまり深く考えないことにします。

テストケースの記述例

<?xml version="1.0" encoding="UTF-8"?>
<suite name="Web Unit Test">
    <driver name="firefox" version="11.0" capabilities=""/>
 
    <test name="トップ">
        <get>http://shorindo.com</get>
        <waitfor timeout="5"/>
        <verifyPresent>
            <text value="松林堂"/>
            <element link="HOME"/>
            <element link="会社案内"/>
            <element link="業務実績"/>
            <element link="研究開発"/>
        </verifyPresent>
    </test>
 
    <test name="会社案内" depends="トップ">
        <click link="会社案内"/>
        <waitfor timeout="5"/>
        <verifyPresent>
            <text value="会社名"/>
            <text value="所在地"/>
            <text value="資本金"/>
        </verifyPresent>
    </test>
 
    <test name="業務実績" depends="会社案内">
	<click link="業務実績"/>
        <waitfor timeout="5"/>
        <verifyPresent>
            <element xpath="//h1/a[text()='業務実績']"/>
            <element xpath="//h2/a[text()='2011年度']"/>
            <element xpath="//h2/a[text()='2010年度x']"/>
        </verifyPresent>
    </test>
</suite>

xmldriver.js

importPackage(java.io);
 
var xmldriver = { countTests:0, countVerify:0 };
xmldriver.trace = function() {
	print(arguments.callee.caller);
};
xmldriver.load = function(fileName) {
	importPackage(javax.xml.parsers);
	importPackage(javax.xml.xpath);
	var xmlFactory = DocumentBuilderFactory.newInstance();
	var builder = xmlFactory.newDocumentBuilder();
	this.document = builder.parse(fileName);
	var xpathFactory = XPathFactory.newInstance();
	this.xpath = xpathFactory.newXPath();
};
xmldriver.queryForNode = function(query, node) {
	var target;
	if (typeof(node) == 'object') target = node;
	else target = this.document;
	var expr = this.xpath.compile(query);
	return expr.evaluate(target, XPathConstants.NODE);
};
xmldriver.queryForList = function(query, node) {
	var target;
	if (typeof(node) == 'object') target = node;
	else target = this.document;
	var expr = this.xpath.compile(query);
	return expr.evaluate(target, XPathConstants.NODESET);
};
xmldriver.info = function() {
	var tests = this.queryForList('/suite/test');
	print("Number of tests=" + tests.length);
	//var verifies = this.queryForList('//verify');
	//print("verify=" + verifies.length);
};
xmldriver.findElements = function(attrs) {
	//print("findElements(" + attrs + ")");
	var result;
	for (var i = 0; attrs && i < attrs.length; i++) {
		var attr = attrs.item(i);
		var name = attr.getName();
		var value = attr.getValue();
		//print("findElements(" + name + "," + value + ")");
		if (name == 'id') {
			result = this.driver.findElementsById(value);
			if (result) break;
		}
		else if (name == 'name') {
			result = this.driver.findElementsByName(value);
			if (result) break;
		}
		else if (name == 'tag') {
			result = this.driver.findElementsByTagName(value);
			if (result) break;
		}
		else if (name == 'class') {
			result = this.driver.findElementsByClassName(value);
			if (result) break;
		}
		else if (name == 'selector') {
			result = this.driver.findElementsByCssSelector(value);
			if (result) break;
		}
		else if (name == 'xpath') {
			result = this.driver.findElementsByXPath(value);
			if (result) break;
		}
		else if (name == 'link') {
			result = this.driver.findElementsByLinkText(value);
			if (result) break;
		}
	}
	return result;
};
xmldriver.findElement = function(attrs) {
	//print("findElement(" + attrs + ")");
	var result = this.findElements(attrs);
	if (result && result.size() > 0) return result.get(0);
};
xmldriver.getAttrsString = function(attrs) {
	//print("getAttrsString(" + attrs + ")");
	if (attrs == null) return;
	var result = '';
	var sep = '';
	for (var i = 0; i < attrs.length; i++) {
		result += sep + attrs.item(i).getName() + '=' + attrs.item(i).getValue();
		sep = ',';
	}
	return result;
};
xmldriver.dump = function(node) {
	if (node.nodeName == '#text') {
		var t = node.textContent + ''; //avoid java string.
		if (!t) return '';
		t = t.replace(/\s+/mg, '');
		if (!t) return '';
		else return t;
	}
 
	var result = '<' + node.nodeName;
	var attrs = node.attributes;
	for (var i = 0; attrs && i < attrs.length; i++) {
		var attr = attrs.item(i);
		result += ' ' + attr.getName() + '="' + attr.getValue() + '"'; 
	}
	if (node.hasChildNodes()) {
		result += '>';
		for (var i = 0; i < node.childNodes.length; i++) {
			var child = node.childNodes.item(i);
			result += this.dump(child);
		}
		result += '</' + node.nodeName + '>';
	} else {
		result += '/>';
	}
	return result;
};
xmldriver.run = function() {
	this.doSuite(this.document.documentElement);
};
xmldriver.quit = function() {
	if (this.driver) this.driver.quit();
}
 
/*
 * XML nodes handling.
 */
xmldriver.doSuite = function(node) {
	if (node.nodeName != 'suite')
		throw new Error('invalid format');
	//print(node.nodeName);
	for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes.item(i);
		if (child.nodeName == 'driver') {
			this.doDriver(child);
		}
		else if (child.nodeName == 'test') {
			this.doTest(child);
		}
	}
};
xmldriver.doDriver = function(node) {
	//print(node.nodeName);
	var driverName = node.getAttribute('name');
	if (driverName == 'firefox') {
		importPackage(org.openqa.selenium);
		importPackage(org.openqa.selenium.firefox);
		this.driver = new FirefoxDriver();
	}
	else if (driverName == 'ie') {
		importPackage(org.openqa.selenium);
		importPackage(org.openqa.selenium.ie);
		this.driver = new InternetExplorerDriver();
	}
	else if (driverName == 'htmlunit') {
		importPackage(org.openqa.selenium);
		importPackage(org.openqa.selenium.htmlunit);
		this.driver = new HtmlUnitDriver(true);
	}
	else {
		throw new Error('valid driver name not specified!');
	}
	this.driver.manage().timeouts().implicitlyWait(10, java.util.concurrent.TimeUnit.SECONDS);
};
xmldriver.doTest = function(node) {
	//print(node.nodeName);
	print((++this.countTests) + ". " + node.getAttribute('name'));
	for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes.item(i);
		if (child.nodeName == 'verify') {
			this.doVerify(child);
		}
		else if (child.nodeName == 'get') {
			this.doGet(child);
		}
		else if (child.nodeName == 'click') {
			this.doClick(child);
		}
		else if (child.nodeName == 'sendkeys') {
			this.doSendKeys(child);
		}
		else if (child.nodeName == 'waitfor') {
			this.doWaitfor(child);
		}
		else if (child.nodeName == 'frame') {
			this.doFrame(child);
		}
		else if (child.nodeName == 'window') {
			this.doWindow(child);
		}
		else if (child.nodeName == 'echo') {
			this.doEcho(child);
		}
		else if (child.nodeName == 'snapshot') {
			this.doSnapShot(child);
		}
	}
};
xmldriver.doGet = function(node) {
	this.driver.get(node.textContent);
};
xmldriver.doClick = function(node) {
	var element = this.findElement(node.attributes);
	if (element) element.click();
	else throw new Error("element[" + this.getAttrsString(node.attributes) + "] not found.");
};
xmldriver.doSendKeys = function(node) {
	//print("doSendKeys(" + this.getAttrsString(node.attributes) + ")");
	var element = this.findElement(node.attributes);
	if (element) element.sendKeys(node.getAttribute("value"));
	else throw new Error("element[" + this.getAttrsString(node.attributes) + "] not found.");
};
xmldriver.doFrame = function(node) {
	this.driver.switchTo().frame(node.getAttribute('name'));
	try {
	    for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes.item(i);
		if (child.nodeName == 'verify') {
			this.doVerify(child);
		}
		else if (child.nodeName == 'get') {
			this.doGet(child);
		}
		else if (child.nodeName == 'click') {
			this.doClick(child);
		}
		else if (child.nodeName == 'sendkeys') {
			this.doSendKeys(child);
		}
		else if (child.nodeName == 'waitfor') {
			this.doWaitfor(child);
		}
		else if (child.nodeName == 'echo') {
			this.doEcho(child);
		}
	    }
	} catch (e) {
	    print(e);
	}
	this.driver.switchTo().defaultContent();
};
xmldriver.doWindow = function(node) {
	this.driver.switchTo().window(node.getAttribute('name'));
	try {
	    for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes.item(i);
		if (child.nodeName == 'verify') {
			this.doVerify(child);
		}
		else if (child.nodeName == 'get') {
			this.doGet(child);
		}
		else if (child.nodeName == 'click') {
			this.doClick(child);
		}
		else if (child.nodeName == 'sendkeys') {
			this.doSendKeys(child);
		}
		else if (child.nodeName == 'waitfor') {
			this.doWaitfor(child);
		}
		else if (child.nodeName == 'echo') {
			this.doEcho(child);
		}
	    }
	} catch (e) {
	    print(e);
	}
	this.driver.switchTo().defaultContent();
};
xmldriver.doEcho = function(node) {
	print("[echo] " + node.textContent);
};
xmldriver.doSnapShot = function(node) {
	/*
	var bytes = this.driver.getScreenshotAs(OutputType.BYTES);
	var fos = new FileOutputStream("img/screenshot" + (new Date()).getTime() + ".png");
	fos.write(bytes, 0, bytes.length);
	fos.close();
	*/ 
};
xmldriver.doWaitfor = function(node) {
};
xmldriver.doVerify = function(node) {
	//print("doVerify()");
	var result = true;
	var count = 1;
	for (var i = 0; i < node.childNodes.length; i++) {
		var child = node.childNodes.item(i);
		var r = false;
		try {
			if (child.nodeName == 'title') {
				r = this.doTitle(child);
			}
			else if (child.nodeName == 'text') {
				r = this.doText(child);
			}
			else if (child.nodeName == 'element') {
				r = this.doElement(child);
			}
			else if (child.nodeName == 'enabled') {
				r = this.doEnabled(child);
			}
			else {
				continue;
			}
		} catch (e) {
			print(e);
		}
		if (r) {
			print("verify success[" + (this.countVerify++) + "] " + this.dump(child));
		} else {
			print("verify failure[" + (this.countVerify++) + "] " + this.dump(child));
			result = false;
		}
	}
	return result;
};
xmldriver.doTitle = function(node) {
	var title = this.driver.getTitle();
	//print("doTitle(" + node.getAttribute('value') + ")<=>" + title);
	if (title == node.getAttribute('value')) return true;
	else return false;
};
xmldriver.doElement = function(node) {
	var el = this.findElement(node.attributes);
	var result = node.getAttribute('present');
	var displayed = node.getAttribute('displayed');
	var enabled = node.getAttribute('enabled');
	var selected = node.getAttribute('selected');
 
	if (result == 'false') result = false;
	else result = true;
	//print("doElement(" + node.nodeName + ")=>" + el);
 
	if (el) {
		//print("element found:" + this.dump(node));
		if (displayed == 'true') result = el.isDisplayed();
		else if (displayed == 'false') result = !el.isDisplayed();
		if (enabled == 'true') result = el.isEnabled();
		else if (enabled == 'false') result = !el.isEnabled();
		if (selected == 'true') result = el.isSelected();
		else if (selected == 'false') result = !el.isSelected();
		return result;
	} else {
		//print("element not found:" + this.dump(node));
		return !result;
	}
};
xmldriver.doText = function(node) {
	var text = node.getAttribute('value');
	var el = this.driver.findElementByXPath('//*[contains(text(),"' + text + '")]');
	var result = node.getAttribute('present');
 
	if (result == 'false') result = false;
	else result = true;
 
	if (el) {
		return result;
	} else {
		return !result;
	}
};
 
 
if (arguments.length > 0) {
	for (var i = 0; i < arguments.length; i++) {
		xmldriver.load(arguments[i]);
		xmldriver.run();
	}
	xmldriver.quit();
} else {
	print("usage: xmldriver.js suite1.xml suite2.xml ..");
}

実行方法

JavaVMの第一引数にRhinoのshell、第二引数にxmldriver.jsプログラム本体、第三引数にテストケースのXMLファイルを指定します。

% java -cp "js.jar;selenium-server-standalone-2.20.0.jar" \
    org.mozilla.javascript.tools.shell.Main \
    xmldriver.js \
    suites.xml 2> xmldriver.log

まとめ

  • 実行結果をきちんと処理できるようにXMLで出力すれば、そこそこ使えそうです。
  • 単純なテストならいいのですが、テスト項目を指定してテストを駆動したり、依存関係の記述ができたほうが便利です。
  • 前処理・後処理をしたい場合、xmldriver.jsに記述を追加すればいいのですが、それではプログラムで実行する方が便利だということになります。
  • つまり、独自のXMLを定義するのではなく、WebDriverの各処理をAntのタスクの1つとして実行させることで、上記の問題が解決できそうです。

関連記事

 
research/1333928078.txt · 最終更新: 2012/04/09 11:10 by Kazuyuki Matsuda
特に明示されていない限り、本サイトの内容は次のライセンスに従います:Copyright(C) 2011 Shorindo, Inc. All Rights Reserved
Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki