Make Watir Support Jquery event
I have been in use watir(1.8.0) and ruby(1.8.7) for Regression test recently. I've got a problem yesterday. Look the demonstration page below:
<html> <head> <script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script> </head> <body> <select id ="s1"> <option value="1">123</option> <option value="2">456</option> <option value="3">789</option> </select> <script> $('#s1').change(function(){ alert('fired'); }); </script> </body> </html>
when I use the code below to hope fire the change event of select element,but it can't work.
require "watir" @browser = Watir::Browser.new @browser.goto('a.html') @browser.select_list(:id, 's1').set('456')
I deep in to the source code of watir(1.8.0),and found the code that:
def select_item_in_select_list(attribute, value) #:nodoc: assert_exists highlight(:set) found = false value = value.to_s unless [Regexp, String].any? { |e| value.kind_of? e } @container.log "Setting box #{@o.name} to #{attribute.inspect} => #{value.inspect}" @o.each do |option| # items in the list if value.matches(option.invoke(attribute.to_s)) if option.selected found = true break else option.selected = true @o.fireEvent("onChange") @container.wait found = true break end end end unless found raise NoValueFoundException, "No option with #{attribute.inspect} of #{value.inspect} in this select element" end highlight(:clear) end
As the code show,Watir calling API of win32ole object,and raise the native event "onChange".but this couldn't fire "change" event which jquery support. It's so tricky!
After a research in google.i got this answer from StackOverFlow.
@browser = Watir::Browser.new @browser.goto("http://someurl") @browser.select_list(:id, element_id).select(item_to_select) @browser.ie.Document.parentWindow.execScript('$("##{element_id}").change();')
it's really cool method and seems resolved my problem(Let's say WATIR SUCKS loudly ). I couldn't use this directly because this is just a demonstration.In production environment,there are a lot of same invoke.To avoid duplication code,i have to extend watir to support our requirement.
...................... ............ option.selected = true @o.fireEvent("onChange") script = %Q{$("##{self.id}").change();} @o.Document.parentWindow.execScript(script) @container.wait found = true break ............. ................
It works well now!
Let's refactor it-OpenSinaAPI
这两天一直在看Clean Code以及 重构,一直想找一些例子练练手。恰巧前两天在玩新浪微博应用的时候看到了陈着写的这个SDK。down下来代码后发现这里面还有很多可以重构的地方,于是花了一个周末实践了一下。
在简单了解了下OAuth的原理及使用后,先确定了重构的范围:
1、基础类oAuthBase.cs不动,因为这个类是由官方提供的。
2、有必要时外部调用接口也需要更改(事实上写到最后几乎对所有的接口都改了)。
开始下手,先看oAuthWebRequest这个函数
public string oAuthWebRequest(Method method, string url, string postData) { string outUrl = ""; string querystring = ""; string ret = ""; postData += "&source=" + appKey; if (method == Method.POST) { if (postData.Length > 0) { NameValueCollection qs = HttpUtility.ParseQueryString(postData); postData = ""; foreach (string key in qs.AllKeys) { if (postData.Length > 0) { postData += "&"; } qs[key] = HttpUtility.UrlEncode(qs[key]); qs[key] = this.UrlEncode(qs[key]); postData += (key + "=" + qs[key]); } if (url.IndexOf("?") > 0) { url += "&"; } else { url += "?"; } url += postData; } } Uri uri = new Uri(url); string nonce = this.GenerateNonce(); string timeStamp = this.GenerateTimeStamp(); //Generate Signature string sig = this.GenerateSignature(uri, this.appKey, this.appSecret, this.token, this.tokenSecret, method.ToString(), timeStamp, nonce, out outUrl, out querystring); querystring += "&oauth_signature=" + HttpUtility.UrlEncode(sig); if (method == Method.POST) { postData = querystring; querystring = ""; } if (querystring.Length > 0) { outUrl += "?"; } if (method == Method.POST || method == Method.GET) ret = _WebRequest(method, outUrl + querystring, postData); return ret; }
这个函数的目的是来创建请求,并且将请求结果返回。
让我们来识别一下这个函数都有哪些bad smell:
1、long method.这个方法足足有70行。
2、Temporary Field.光函数开头声明的临时变量就有3个,函数内部声明的临时变量更是不尽其数。
3、Single Responsibility Principle.函数中充斥着大量的if-else,以及多处判断POST与Get。
4、Higher Cyclomatic Complexity.函数中存在可以避免的嵌套 if-else
重构的方法:
首先根据不同的请求类型(POST/GET)来声明不同的处理方法,为了使客户类调用行为一致,我这里抽取出了一个IHttpRequestMethod接口
public interface IHttpRequestMethod { string Request(string uri, string postData); }
其中Request方法相当于原库中oAuthWebRequest,分别用两个类HttpGet与HttpPost来实现。
然后是将这个方法拆解成为几个小方法。最后的代码如下:
public class HttpPost : BaseHttpRequest { ..... ...... public override string Request(string uri, string postData) { var appendUrl = AppendPostDataToUrl(postData, uri); string outUrl; var querystring= AppendSignatureString(POST, appendUrl, out outUrl); return WebRequest(POST, outUrl, querystring); } ...... }
public class HttpGet : BaseHttpRequest { ....... ...... public override string Request(string uri, string postData) { string outUrl; var queryString = AppendSignatureString(GET, uri, out outUrl); if (queryString.Length > 0) { outUrl += "?"; } return WebRequest(GET, outUrl + queryString); } ...... }
全部代码参见:
http://code.google.com/p/opensinaapi/downloads/detail?name=OpenSinaApi04-18-2011.rar
欢迎各位看官一起动手来进一步重构:)