FluentWait with WebElement

原文-FluentWait with WebElement

我在 WebDriver 上的同步策略一般都是围绕 WebDriverWait 和 自定义 ExpectedCondition 来做文章。当我第一眼看到 FluentWait,
我以为这是一个自定义化更多的一个 WebDriverWait。 但是当我仔细研究它的构造函数的时候,我注意到它使用了 Generics (范型)。
Generics 的使用意味着我不需要再传一个 WebDriver 了, 我可以使用 WebElement 或者 By 或者任何我想要的。 这样对于 Ajax 的应用,真是方便了不少。

org.openqa.selenium.support.ui
Class FluentWait\

java.lang.Object
org.openqa.selenium.support.ui.FluentWait\

Type Parameters:
T - The input type for each condition used with this instance.

All Implemented Interfaces:
Wait\

Direct Known Subclasses:
WebDriverWait

看下面这段代码, 我用了一个倒计时的计数器做例子。

这段代码实现了:

  1. 用一个 WebDriverWait 等待元素出现。
  2. 看计数器是否倒数到 04.
1
2
3
4
5
6
7
8
9
10
11
12
13
WebElement countdown = driver.findElement(By.id("javascript_countdown_time"));
new WebDriverWait(driver,10).until(ExpectedConditions.visibilityOf(countdown));
new FluentWait<WebElement>(countdown).withTimeout(10, TimeUnit.SECONDS).
pollingEvery(100,TimeUnit.MILLISECONDS).
until(new Function<WebElement , Boolean>() {
@Override
public Boolean apply(WebElement element) {
return element.getText().endsWith("04");
}
}
);

注意在上述代码中,我告诉 FluentWait, apply 方法里接收的将是一个 WebElement。FluentWait 可以接受一个 Predicate 或者 Function 做为 until 方法的参。
我用了 Function 并在 这个 Function 里面返回一个 Boolean。

FluentWait 非常灵活,你可以很轻松的调整等待和轮询时间, 你也可以设置哪些 exceptions 可以忽略, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
// Waiting 30 seconds for an element to be present on the page, checking
// for its presence once every 5 seconds.
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(30, SECONDS)
.pollingEvery(5, SECONDS)
.ignoring(NoSuchElementException.class); // 我们主动忽略了 NoSuchElementExceptions 的异常
WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("foo"));
}
});

怎样,看上去是不是优雅多了?

Gatling 试用 1

介绍


在微博上看到 Gatling,号称超越 jmeter 的性能测试工具。顿时手痒,想来个试用。

Gatling 格林机关枪,看名字就恨凶悍。

Gatling 的动机是因为性能测试工具已经无法测试当今的应用了。

比如:

  1. LoadRunner 太贵了。
  2. 页面又改了,又要打开界面,重新录制。
  3. 客户说用 OpenSTA , 我们只能用 windows 的虚拟机。
  4. Jmeter 太弱了,并发的好假,还得建立集群,而且还不知道每个节点的信息能不能回收。。
  5. Grinder 又内存泄露了,苦逼的 python 开发。。。(不知道是哪个工具)

看来业界对市场上的性能测试工具都恨之入骨。

再来看下 Gatling:

  1. 高效
    Gatling 用了 actors 模型,真是一个好演员啊。

    Actor模型

    当一个请求来了之后, 则委派给一个Actor处理, 和事件驱动的区别是, 这些Actor之间不共享任何资源, 可进行并行处理, 事件驱动模型和Actor模型很容易被搞混淆, 如果事件驱动模型来一个请求则委派一个线程去处理并且这些线程之间不共享资源,不会出现锁的问题, 则可以认为这种事件驱动模型等价于Actor模型, Scala的Actor模型就是这么处理的, Actor会被代理到线程处理, 海量的Actor会被代理到一个JVM线程池处理,这样提升了很大的效率,而这种映射是scala帮你做好了; 而Erlang中Actor模型则更加彻底, 其实是OTP实现了操作系统中的线程的功能, 这些“线程”叫做process, 只不过这些process占用的资源很少, 也不共享任何资源, 这样每个actor可以跟process一一对应

  2. Gatling 脚本是代码,而且优雅简洁

    其实就是 scala 和 scala 写的 DSL, 不觉得 scala 优雅啊。。。

  3. Gatling 的报表很有意义。

  4. Gatling 免费的,明显最重要的。

上手


略过下载安装之类的,实在要看,自己看 Getting-started 吧。
我们按照 First Steps with Gatling 走一遍。
这个 First Steps 就是测试一个 e-banking 的应用,很可惜这个应用放在 Cloud Foundry, 国内估计访问不了。

这个应用的链接在:

http://excilysbank.gatling.cloudbees.net

主要实现了以下功能:

  1. Session management (login, logout)
  2. Accounts visualization
  3. Operations visualization by month
  4. Cards Operations visualization by month
  5. Transfers visualization and transfer performing

测试计划

我们对一些具有代表性的场景进行性能测试,所以我们想象了下,用户会做的事情:

  1. 用户访问应用
  2. 用户获得认证并登陆
  3. 用户访问具体的账号
  4. 用户查看上个月的状况
  5. 用户登出

我们要用 Gatling 来录制这 5 个场景。

Gatling Recorder

一般工具都提供 recorder 否则就弱爆了。

1
~$ $GATLING_HOME/bin/recorder.sh

启动这个脚本之后,会跳出一个类似 jmeter 的界面,我们得配置下:

  • package: com.excilys.ebi.bank.stress
  • class name: MySimulation
  • output folder: /Users/lihuazhang/Documents/Simulation

alt text

和 jmeter 一样, Gatling 的 recorder 一样也充当着浏览器和应用之间 proxy 的角色,从上图来看:

  • http 8000
  • https 8001

所以必须在浏览器设置代理。然后在浏览器上的所有 request 都会被记录下来。当然 Gatling 也提供了 Filters 来过滤
无关信息:

  • NONE: Doesn’t apply any filter one the requests, even if there are some declared;
  • EXCEPT: Allows all requests to be logged except those which filters apply on the URI;
  • ONLY: Allows only requests, which URI matches one of the filters, to be logged.

更加具体的,不妨看下 Recorder details

开始录制吧

设置好代理之后,开始执行第一个场景:

  1. 访问 http://excilysbank.gatling.cloudbees.net

  2. 登陆

    username = user1

    password = password1

  3. Click on PERSONNAL_CHECKING

  4. Click on the previous month (for us it was November as we were in December)

  5. Log out

然后点击 Recorder 上的 stop, 就会在之前设置的 output folder 里面生成 MySimulation.scala 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.excilys.ebi.bank.stress
import io.gatling.core.Predef._
import io.gatling.core.session.Expression
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.gatling.http.Headers.Names._
import io.gatling.http.Headers.Values._
import scala.concurrent.duration._
import bootstrap._
import assertions._
class MySimulation extends Simulation {
val httpProtocol = http
.baseURL("http://excilysbank.gatling.cloudbees.net")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3")
.connection("keep-alive")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0")
val headers_1 = Map("""Cache-Control""" -> """max-age=0""")
val headers_3 = Map("""Accept""" -> """text/css,*/*;q=0.1""")
val headers_4 = Map("""Accept""" -> """image/png,image/*;q=0.8,*/*;q=0.5""")
val headers_6 = Map("""Accept""" -> """*/*""")
val headers_8 = Map("""Content-Type""" -> """application/x-www-form-urlencoded""")
val headers_11 = Map(
"""Accept""" -> """application/json, text/javascript, */*; q=0.01""",
"""X-Requested-With""" -> """XMLHttpRequest""")
val scn = scenario("Scenario Name")
.exec(http("request_1")
.get("""/""")
.headers(headers_1))
.pause(2)
.exec(http("request_2")
.get("""/img/favicon.ico"""))
.pause(648 milliseconds)
.exec(http("request_3")
.get("""/gzip_N2030079394/bundles/all.css""")
.headers(headers_3))
.pause(819 milliseconds)
.exec(http("request_4")
.get("""/cb1024132465/img/background.png""")
.headers(headers_4))
.pause(23 milliseconds)
.exec(http("request_5")
.get("""/cb4077127084/img/small_window_content.png""")
.headers(headers_4))
.pause(2)
.exec(http("request_6")
.get("""/gzip_N762261864/bundles/all.js""")
.headers(headers_6))
.pause(1)
.exec(http("request_7")
.get("""/cb3761658453/img/sprite.png""")
.headers(headers_4))
.pause(30)
.exec(http("request_8")
.post("""/login""")
.headers(headers_8)
.param("""username""", """user1""")
.param("""password""", """password1"""))
.pause(3)
.exec(http("request_9")
.get("""/cb2545837452/img/big_window_content.png""")
.headers(headers_4))
.pause(21)
.exec(http("request_10")
.get("""/private/bank/account/ACC1/operations.html"""))
.pause(3)
.exec(http("request_11")
.get("""/private/bank/account/ACC1/year/2012/month/4/page/0/operations.json""")
.headers(headers_11))
.pause(43)
.exec(http("request_12")
.get("""/private/bank/account/ACC1/year/2012/month/3/operations.html"""))
.pause(663 milliseconds)
.exec(http("request_13")
.get("""/private/bank/account/ACC1/year/2012/month/3/page/0/operations.json""")
.headers(headers_11))
.pause(4)
.exec(http("request_14")
.get("""/logout"""))
setUp(scn.inject(atOnce(1 user))).protocols(httpProtocol)
}

代码分析

MySimulation class 可以分为 4 部分:

  1. The HTTP protocol configuration
  2. The headers definition
  3. The scenario definition
  4. The simulation definition

The HTTP protocol configuration

1
2
3
4
5
6
7
val httpProtocol = http
.baseURL("http://excilysbank.gatling.cloudbees.net")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3")
.connection("keep-alive")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0")

除了设置 http 协议之外,最重要的就是指定了 baseUrl

The headers definition

1
2
3
4
5
6
7
8
9
10
11
12
13
val headers_1 = Map("""Cache-Control""" -> """max-age=0""")
val headers_3 = Map("""Accept""" -> """text/css,*/*;q=0.1""")
val headers_4 = Map("""Accept""" -> """image/png,image/*;q=0.8,*/*;q=0.5""")
val headers_6 = Map("""Accept""" -> """*/*""")
val headers_8 = Map("""Content-Type""" -> """application/x-www-form-urlencoded""")
val headers_11 = Map(
"""Accept""" -> """application/json, text/javascript, */*; q=0.01""",
"""X-Requested-With""" -> """XMLHttpRequest""")

每一个 header 都是一个 scala 的 Map。

这些通用的 header 信息看上去没用,其实这些信息也额外增加了负载,也不容忽视的。

The scenario definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
val scn = scenario("Scenario Name")
.exec(http("request_1")
.get("""/""")
.headers(headers_1))
.pause(2)
.exec(http("request_2")
.get("""/img/favicon.ico"""))
.pause(648 milliseconds)
.exec(http("request_3")
.get("""/gzip_N2030079394/bundles/all.css""")
.headers(headers_3))
.pause(819 milliseconds)
.exec(http("request_4")
.get("""/cb1024132465/img/background.png""")
.headers(headers_4))
.pause(23 milliseconds)
.exec(http("request_5")
.get("""/cb4077127084/img/small_window_content.png""")
.headers(headers_4))
.pause(2)
.exec(http("request_6")
.get("""/gzip_N762261864/bundles/all.js""")
.headers(headers_6))
.pause(1)
.exec(http("request_7")
.get("""/cb3761658453/img/sprite.png""")
.headers(headers_4))
.pause(30)
.exec(http("request_8")
.post("""/login""")
.headers(headers_8)
.param("""username""", """user1""")
.param("""password""", """password1"""))
.pause(3)
.exec(http("request_9")
.get("""/cb2545837452/img/big_window_content.png""")
.headers(headers_4))
.pause(21)
.exec(http("request_10")
.get("""/private/bank/account/ACC1/operations.html"""))
.pause(3)
.exec(http("request_11")
.get("""/private/bank/account/ACC1/year/2012/month/4/page/0/operations.json""")
.headers(headers_11))
.pause(43)
.exec(http("request_12")
.get("""/private/bank/account/ACC1/year/2012/month/3/operations.html"""))
.pause(663 milliseconds)
.exec(http("request_13")
.get("""/private/bank/account/ACC1/year/2012/month/3/page/0/operations.json""")
.headers(headers_11))
.pause(4)
.exec(http("request_14")
.get("""/logout"""))

一个simluation 里面可以定义一个或者多个 scenario, 每个 scenario 是一个 scala 对象,

1
val scn = scenario("Name of my Scenario")...

scenario 有两个方法:

  • exec - describe an action, usually a request sent to the tested application
  • pause - simulate the thinking time of the user between pages

每个 scenario 里会发送很多个 request, 也会思考好多次。所以你看到上面的代码都是 exec http request 和 pause。

HTTP requests 的定义包括 名字,post 或者 get 方法,headers, 还有参数 :

1
2
3
4
5
http("request_3")
.post("/login")
.headers(headers_3)
.param("username", "user1")
.param("password", "password1")

这段代码会发送一个 POST 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://excilysbank.gatling.cloudbees.net/login
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: fr,en-us;q=0.7,en;q=0.3
Host: excilysbank.gatling.cloudbees.net
Keep-Alive: 115
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/9.10 Firefox/3.6.17
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Referer: http://excilysbank.gatling.cloudbees.net/public/login.html
username=user1&password=password1

The simulation definition

1
setUp(scn.inject(atOnce(1 user))).protocols(httpProtocol)

运行下吧!

首先我们要把这个 Simulation move 到 user-files/simulations/下。

然后运行$GATHING_HOME/bin/gatling.sh, Gatling 会找到 MySimulation。

选择 MySimulation,一路回车,然后等待运行结束,生成 report:

分析报表吧!

Gatling 生成的报表非常漂亮而且有意义。每个图表的具体含义可以参考 Reports
这样子的报表几乎可以直接呈给领导,都不需要自己采集数据绘图了。

下一节

到目前为止,我们都是照搬 Gatling wiki 来做的,其实就了解了下基本情况。下一节,应该搞个 project 试验下。

add hover event to capybara

Seems capybara has implemented the hover function

check capybara/selenium/node.rb

1
2
3
4
5
6
7
8
9
10
class Capybara::Selenium::Node < Capybara::Driver::Node
...
def hover
driver.browser.action.move_to(native).perform
end
...
end

But it’s only a selenium implementation. Not all the browsers implement hover function,
Capybara has not apply the method.

check capybara/session.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NODE_METHODS = [
:all, :first, :attach_file, :text, :check, :choose,
:click_link_or_button, :click_button, :click_link, :field_labeled,
:fill_in, :find, :find_button, :find_by_id, :find_field, :find_link,
:has_content?, :has_text?, :has_css?, :has_no_content?, :has_no_text?,
:has_no_css?, :has_no_xpath?, :resolve, :has_xpath?, :select, :uncheck,
:has_link?, :has_no_link?, :has_button?, :has_no_button?, :has_field?,
:has_no_field?, :has_checked_field?, :has_unchecked_field?,
:has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
:has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector
]
SESSION_METHODS = [
:body, :html, :source, :current_url, :current_host, :current_path,
:execute_script, :evaluate_script, :visit,
:within, :within_fieldset, :within_table, :within_frame, :within_window,
:save_page, :save_and_open_page, :save_screenshot,
:reset_session!, :response_headers, :status_code,
:title, :has_title?, :has_no_title?, :current_scope
]
DSL_METHODS = NODE_METHODS + SESSION_METHODS

You have two ways to use the hover function:

  1. convert element to selenium element and call hover

    element.native.hover

  1. Add the snippet below to hook.rb file under the support directory.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Example:
    #
    # find("span.assign").hover
    #
    module Capybara
    module Node
    class Element
    def hover
    @session.driver.browser.action.move_to(self.native).perform
    end
    end
    end
    end

读陆犯焉识

妻子悄悄问:“他回来了吗?” 丈夫于是明白了,她打听的是她一直在等的那个人,虽然她已经忘了他的名字叫陆焉识。 “回来了。”丈夫悄悄地回答她。 “还来得及吗?”妻子又问。 “来得及的。他已经在路上了。” “哦,路很远的。” 婉喻最后这句话是袒护她的焉识;就是焉识来不及赶到也不是他的错,是路太远。

不得不说严歌苓写人物很厉害,行云流水间,
把人物的一颦一笑展现在读者眼前,有时浓郁,有时淡雅。小姨多鹤如此,陆犯焉识也如此。

一口气把陆犯焉识读完,地铁上,马桶上,床上,然后不停的像身边人推荐,来读读看吧。
朋友会问有什么好的? 说不上来,我不是一个擅长评价的人,特别是书籍电影之类的,
看完之后,常常就两个评价,好看。我一直以为一部好的作品,最大的作用就是加速无聊时间。
在地铁始发站,找个位置,然后开始读,没几章,目的地到了,心想还没看够呢。然后心满意足地继续看。

这总归是一本幸福的书,人们看事情,总是看一个头看一个尾,开头是幸福的,结尾是幸福的,所以
全部都是幸福的。失败的人才会把过程拿出来说事。陆焉识从没觉得过不开心,他最多觉得不自在,这是知识分子的臭老九。
女人的离开,好友的翻脸,政治的迫害,对他而言就是人生的一种经历,经过了,就留下幸福的回忆。到最后,婉喻去了,就剩下
老几在回忆。

这总归也是一本有关知识分子和政治的书。陆焉识的人生和政治碰撞交织融汇,知识分子的尊严碎了一地,自由的只有思维,
20 年的大西北出来了一个抖抖索索的老几,饶是如此,内心还是知识分子。给小孩们补课的时候,还不忘讽刺下毛主席。
谁对谁错呢?谁也不敢说,但我想陆焉识还是更适合美国。

这当然也是一本关于爱情的书,三段爱情,一段异国恋,两段婚外恋,还有一段异地恋。这段异地恋持续了 20 年,并越来越浓烈。

我的 nexus 7 2013 软件列表

首先感谢小郭,感谢美帝,没有你们,我就不会有 nexus 7 2013。
其实我一直想要台 Android 的平板,早在 2011 年的时候,当时同办公室的同事做 google image 测试,发了台 moto 的 xoom 做测试机,我就眼红不已。
在 2012 年的时候,我让小立给我带过一个 nexus 7 2012, 结果被同事很喜欢,就转给他了。

可想而知: 一个人想得到一个平板,这其中需要无数的机缘巧合。

这台奶骑(网上人都这么叫-_-!),我拿到手差不多一个礼拜,新买粪坑三天香的热情也退了。中间装过无数软件,也卸载过无数软件,
也发现了几个问题。于是想总结下,一来以后自己可以用到,二来也和大家分享下。

###问题:

  1. 外壳脆弱

    nexus 7 2013 很轻很薄,所以显得特别柔弱,用力捏下,可以听见塑料的声响。当然塑料也很嫩,磕一下就一个小坑。

  2. 触屏失灵

    在国外网上看到的是,多点触摸有问题,会触发别的按键。这个我还没遇到,但是我遇到的是,触屏失灵,没有反应,在重新打开屏幕之后就恢复了。
    不确定是硬件还是软件的问题。

  3. 按钮生硬

    无论是电源按钮还是音量按钮,都硬的很,没有手感可言。

  4. 应用太少

    HD 的应用实在太少了,相比苹果系列, Android 真是要加油啊!

其实做为第一批, google 和 asus 做的还算不错啦, 虽然网上都说 google + asus = bad quality。

###应用:

  • 音乐视频:

    • 豆瓣FM
    • 虾米音乐
    • MoboPlayer
    • MXPlayer (有广告)
    • 快手看片
    • 哗哩哗哩动画
    • PPTV HD
    • QQ 音乐 (我用的时候音乐断断续续过一次)
  • 阅读工具:

    • 多看阅读 (强烈推荐,买了好多书了。不过前面说到触屏失去反应也有可能是多看造成的。)
    • 布卡漫画 (强烈推荐,宅男腐女必备)
    • Kindle
    • Adobe Reader
    • Flipboard
    • 鲜果网
    • 网易云阅读
  • 聊天社交:

    • QQ hd
    • 微信
    • Skype
    • 豆果网
    • 大众点评
    • weico 实在找不到好的微博的应用
  • 工具类:

    • ES 文件浏览器 (强烈推荐)
    • 爱壁纸 HD
    • 有道词典
    • 墨迹天气
    • 金山快盘
  • 游戏:

    • Doodle Fit2

以上应用如果不是指明 HD 的,基本就是矮子拔将军,但是效果不错哦!其实这些软件中有很多都有 HD 版本的,可惜都是一年半载不更新的那种,可惜了。

总结

对我而言,平板的主要用途就是看书,看视频,相对而言上面的这些应用也足够了。当然这也是我一家之言,我也到底没有把所有的应用都体验过一把,所以希望有好用的,
请大力推给我

vim 插件推荐

早上起来逛 ruby china, 看到 vim 插件推荐,进去一看果然有料。

  • vim-easy-align
  • tcomment_vim

果断把我的 tabular 和 nerdcommenter 替换掉。

vim-easy-align

地址: https://github.com/junegunn/vim-easy-align

比任何一个对齐插件都简单啊,不愧是 easy

tcomment_vim

地址: https://github.com/tomtom/tcomment_vim

直接看介绍吧, http://vimsomnia.blogspot.com/2010/11/tcomment-vim-plugin.html

这是一个快速注释的插件,有自动识别文件类型(甚至是嵌套的类型)等的功能,感觉比 nerdcommenter 好很多

默认的快捷键是 gcc, 其实我就用那么一个。。。

Install vim74 from source

Vim 7.4 released!

What are you waiting for?


install vim7.4 for mac

HomeBrew has update the vim formula to the latest 7.4! woo!

See vim.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
require 'formula'
class Vim < Formula
homepage 'http://www.vim.org/'
# Get the base 7.4 tarball from Vim.org. But once patches start again, go
# back to tracking Debian unstable here:
# http://ftp.de.debian.org/debian/pool/main/v/vim/
url 'http://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2'
sha1 '601abf7cc2b5ab186f40d8790e542f86afca86b7'
head 'https://vim.googlecode.com/hg/'
# We only have special support for finding depends_on :python, but not yet for
# :ruby, :perl etc., so we use the standard environment that leaves the
# PATH as the user has set it right now.
env :std
LANGUAGES = %w(lua mzscheme perl python tcl ruby)
DEFAULT_LANGUAGES = %w(ruby python)
option "override-system-vi", "Override system vi"
option "disable-nls", "Build vim without National Language Support (translated messages, keymaps)"
LANGUAGES.each do |language|
option "with-#{language}", "Build vim with #{language} support"
option "without-#{language}", "Build vim without #{language} support"
end
depends_on :hg => :build if build.head?
depends_on :python => :recommended
def install
ENV['LUA_PREFIX'] = HOMEBREW_PREFIX
language_opts = LANGUAGES.map do |language|
if DEFAULT_LANGUAGES.include? language and !build.include? "without-#{language}"
"--enable-#{language}interp"
elsif build.include? "with-#{language}"
"--enable-#{language}interp"
end
end.compact
opts = language_opts
opts << "--disable-nls" if build.include? "disable-nls"
# Avoid that vim always links System's Python even if configure tells us
# it has found a brewed Python. Verify with `otool -L`.
if python && python.brewed?
ENV.prepend 'LDFLAGS', "-F#{python.framework}"
end
# XXX: Please do not submit a pull request that hardcodes the path
# to ruby: vim can be compiled against 1.8.x or 1.9.3-p385 and up.
# If you have problems with vim because of ruby, ensure a compatible
# version is first in your PATH when building vim.
# We specify HOMEBREW_PREFIX as the prefix to make vim look in the
# the right place (HOMEBREW_PREFIX/share/vim/{vimrc,vimfiles}) for
# system vimscript files. We specify the normal installation prefix
# when calling "make install".
system "./configure", "--prefix=#{HOMEBREW_PREFIX}",
"--mandir=#{man}",
"--enable-gui=no",
"--without-x",
"--enable-multibyte",
"--with-tlib=ncurses",
"--enable-cscope",
"--with-features=huge",
"--with-compiledby=Homebrew",
*opts
system "make"
# If stripping the binaries is not enabled, vim will segfault with
# statically-linked interpreters like ruby
# http://code.google.com/p/vim/issues/detail?id=114&thanks=114&ts=1361483471
system "make", "install", "prefix=#{prefix}", "STRIP=/usr/bin/true"
ln_s bin+'vim', bin+'vi' if build.include? 'override-system-vi'
end
end

Just use

1
brew install vim --with-python --with-ruby --with-perl --with-lua --with-tcl

Wait or take a coffee break! And enjoy vim 7.4.

By the way, when you want to update brew but failed because of some formula you’ve editted which results in merge…

1
2
3
cd `brew --prefix`
git checkout .
brew update

install vim7.4 for ubuntu

A little complex but very easy!

  • install the dependencies
1
2
3
sudo apt-get install libncurses5-dev libgnome2-dev libgnomeui-dev \
libgtk2.0-dev libatk1.0-dev libbonoboui2-dev \
libcairo2-dev libx11-dev libxpm-dev libxt-dev python-dev ruby-dev libperl-dev

Note. I’m not very clear about the dependencies but python-dev ruby-dev libperl-dev are for python, ruby, perl interp.
For me, that’s enough.

  • remove the old vim
1
sudo apt-get remove vim vim-runtime gvim vim-tiny vim-common vim-gui-common
  • download the source and compile to install
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
wget http://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2
tar xvf vim-7.4.tar.bz2
cd vim
./configure \
--prefix=/usr \
--enable-perlinterp=dynamic \
--enable-pythoninterp \
--enable-rubyinterp \
--enable-luainterp=dynamic \
--enable-cscope \
--enable-gui=auto \
--enable-gtk2-check \
--enable-gnome-check \
--enable-multibyte \
--enable-fontset \
--with-features=huge \
--with-x \
--with-ruby-command=/usr/bin/ruby \
--with-compiledby="Lihuazhang <lihuazhang@hotmail.com>" \
--with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu \
# --with-python3-config-dir=/usr/lib/python3.3/config-3.3m-x86_64-linux-gnu

Wait for a seconds and sudo make install once the configure is compelted.

  • set your vim as your default editor
1
2
3
4
5
# The prefix=/usr should be specified!
sudo update-alternatives --install /usr/bin/editor editor /usr/bin/vim 1
sudo update-alternatives --set editor /usr/bin/vim
sudo update-alternatives --install /usr/bin/vi vi /usr/bin/vim 1
sudo update-alternatives --set vi /usr/bin/vim

Then done. Enjoy vim7.4.


I have personally tested both installation.

If you have any question, please ping me. Or

Refer:

  1. http://vim.wikia.com/wiki/Building_Vim
  2. https://github.com/Valloric/YouCompleteMe/wiki/Building-Vim-from-source

由 step definition 失控引发的讨论和思考

引子

公司在新项目上 BDD 的开发模式,迄今已经差不多 2 个月了。
按最初的分工,开发写 rspec, 测试写 cucumber,双线并行。
加上最新配置好的 jenkins ci 环境,整个开发流程走的很顺,
代码质量也得到了很好的控制。

今天按老样子写某个 component 的新加的feature的场景,
写 step definition 的时候,发现 stepdef.rb 文件已经
300 多行了。 瞬间有种再不控制就爆炸的感觉。

于是就把问题抛了出去:

cucumber 的 step definition 爆了。。。 越写越多,真难控制!

讨论

我将问题抛在了 TestCirle 的群里,讨论地断断续续从下午一点到下午六点。我将每个人的发言总结下。

MIC-Seeyon

step_definition 之上抽象一些 common function,再之上是 page object 的应用。
这样基本就封装了大部分逻辑了。这样就算 step_definition 再有多,也不会有多少代码吧~

如果有页面很多 ajax 操作,你可以把 ajax 的基础页写成 basePage,
然后其他的 ajax 业务写成不同的 page,继承 basePage 即可~

如果觉得实现太重的话,可以分工程,通用的抽取出来,其他的不同模块或者业务,基于这部分 common 的,搞自己的 feature,自己实现~
这样对具体的编码的人来说就轻了~

把握好 feature 的度, 整理一下测试用例的执行场景哦~

step definition 里面有太多冗余的东西的话就会很重,需要去冗。
如果有的话,那也得做一层基本的网页操作的抽象~
我是说业务和交互级别的 actions
比如根据 label 操作 input
各种条目的选择,操作,内容获取
这些可能都跟你产品的前段设计有关哦~

我们是这么做的:

  1. webdriver 的 api 操作封装一层
  2. 然后产品使用的前段控件封装一层
  3. 然后业务级别的逻辑跟操作封装一层
  4. 然后常用的操作组合封装一个层
  5. 最后才是 step definition

cucumber就是一个驱动自动化测试的粘合剂

横刀天笑

逻辑肯定是在 page object 里的, page object 应该暴露业务接口,既然暴露业务接口,那肯定是包含业务逻辑的
比如我们酒店首页 HotelHomePage。有个搜索功能,那 肯定提供一个 search 的方法,search 里接受一个查询条件

page object,应该从用户角度来看,你这个页面提供了哪些功能,从业务功能上来看 po,而不是从操作上, 然后 step defs 里是流程的描述

持续交付里有一章讲自动化测试, 里面讲了一个三层:

  1. 测试描述层(case)
  2. 测试实现层,或者叫流程层
  3. 应用接口层(比如使用 selenium 或 httpclient 之类的和应用程序打交道)

插曲 -_-!

如何做一个能害死人的自动化测试工具
http://www.ltesting.net/ceshi/ceshijishu/zdcs/2013/0806/206547.html?1375845711

思考

我回想了下,这个项目一开始没采用 page object, 因为:

  1. 以为项目不复杂,参考另一个公司 rails 项目的 cucumber features。
  2. 使用了 pickle,导致 feature 撰写的时候, 有过多的 code 色彩,如果使用 page object 的话 就有点混搭的感觉,不伦不类了。
  3. 最早之前用 page object 有些厌倦了。 特别 ajax render 的页面,导致继承 base 的页面过多。

关于第一点:

接触过很多 rails 的项目, 往往是将 cucumber features 作为 High level 的 acceptance test, 挑选的场景的粒度都是比较粗。
而且 rails 项目的 cucumber 基本都是开发来写的,我一开始就把自己定位为开发。

关于第二点:

pickle 非常强大

Pickle gives you cucumber steps that create your models easily from
factory-girl, machinist, or fabrication. You can also just use ActiveRecord as a factory but it’s not as cool.

但是使用 pickle 的前提是你必须了解 model, 了解各种 model 的 factory, 也就是其实你已经
是从开发的角度来考虑如何测试代码了。 你在 steps 里面的数据设计,其实都是你知道的结果,并不是测试出来的。

pickle 的使用,使 Given, WhenAnd 对 测试和 stakeholder 不太友好。 这不像 page object 的风格。

关于第三点

我在前面一个项目中应用了 Page Object, 我在博客里也粗略地介绍过, 参考 webdriver
由于我们项目大量使用 ajax, 导致基于 basePage 的子页面各种各样的特别多。个人感觉也不太舒服。

综上,所以一开始就忽略了 page object。

目前

我们现在仅仅把 features 进行了分类(利用目录结构),将所有的 step definition 都放在了一个 rb 文件里。随着以后的不同的场景,失控也是时间问题。

结论

目前看来, 这个项目的 cucumber 想要完成的是 整个 UI 的自动化测试。 要去冗,去重需要做的是:

  1. 合并和参数化
  2. Page Object

git revert

最近一直在 github 上提交代码,所以总会遇到提交的代码 broken 了之前的 function。

Perhaps you didn’t have enough coffee that morning, or it was just before lunch.

其实这还无关紧要,再提交一个修复下就可以了。 但是也会遇到比较头疼的, 比如:

注意蓝色这条线的 merge 那个点,开发同学因为粗心忘记把自动 merge 的代码提交了。
黑色这条线的同学,如果 pull 下来,就会发现一部分代码回到了蓝线的起点时候的状态。
然后呢,黑线这边的 merge 就会导致部分代码 rollback 了。很悲催吧。这个时候呢,提交修复也不太可能,最好的办法就是 revert 了。

概念

git revert 命令撤销一个 commit。它的做法是提交一个新的 commit, 这个 commit 里面将你要回滚的 commit 的修改全部撤销。
所以它不会破坏你的历史。如图所示:提交一个新的 commit (蓝球), 撤销灰球修改。

Usage

1
git revert <commit>

Example

http://gitready.com/intermediate/2009/03/16/rolling-back-changes-with-revert.html

假设我们有这样一个 git 历史:

我们来一次 revert

1
2
3
4
$ git revert HEAD
Finished one revert.
[master]: created 1e689e2:
"Revert "Updated to Rails 2.3.2 and edge hoptoad_notifier""

revert 命令会自动帮你 commit, 并为你生成 commit 信息: Revert + 被回滚的 commit 的信息。
这里就是 “Revert “Updated to Rails 2.3.2 and edge hoptoad_notifier””

再来看下 revert 后的历史图:

可以看到 revert 的在最前面添加了一个 commit。

By default using this command will bring up your $EDITOR so you can modify the commit message of the revert.
Use this to explain why the changes are being backed out!
If for some reason you don’t want to edit the commit message, tacking on --no-edit will allow you do so.

另外如果你不想 commit 的话,还可以用 -n 参数

Another helpful option is the -n or –no-commit flag.
This will simply leave the reverted changes in your working directory and not automatically commit them.
If you need to edit files further to complete the revert or possibly revert more than one commit, try out this option.

接着,我们是试试看 revert 一个 merge 的 commit

1
2
$ git revert HEAD~1
fatal: Commit 137ea95 is a merge but no -m option was given.

它告诉你 commit 137ea95 是一个 merge, 需要你指定一个 -m 的option。

-m parent-number, –mainline parent-number
Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline.
This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.

意思是,你需要指定一个 parent number (从1开始),然后 revert 会将基于这个 parent number 的改变都回滚。

再来看下 merge commit 的信息

1
2
3
4
5
6
7
$ git log HEAD~1 -1
commit 137ea95c911633d3e908f6906e3adf6372cfb0ad
Merge: 5f576a9... 62db4af...
Author: Nick Quaranto <nick@quaran.to>
Date: Mon Mar 16 16:22:37 2009 -0400
Merging in rails-2.3 branch

可以看到 5f576a962db4af 被 merge 了。

所以

  1. git revert HEAD~1 -m 1 就会 revert 基于 5f576a9 所发生的变化
  2. git revert HEAD~1 -m 2 就会 revert 基于 62db4af 所发生的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
$ git revert HEAD~1 --no-edit --mainline 2
Finished one revert.
[master]: created 526b346: "Revert "Merging in rails-2.3 branch""
$ git log -1
commit d64d3983845dfa18a5d935c7ac5a4855c125e474
Author: Nick Quaranto <nick@quaran.to>
Date: Mon Mar 16 19:24:45 2009 -0400
Revert "Merging in rails-2.3 branch"
This reverts commit 137ea95c911633d3e908f6906e3adf6372cfb0ad, reversing
changes made to 62db4af8c77852d2cc9c19efc6dfb97e0d0067f5.

很明确的告诉你, 这个 revert 提交了一个 commit: 137ea95c911633d3e908f6906e3adf6372cfb0ad,这个 commit 把 merge 操作对 62db4af8c77852d2cc9c19efc6dfb97e0d0067f5 发生的改变都回滚了。

通俗说法

  1. 普通的 revert: 比如桌上有一碗饭, 我吃完了,然后我又把这碗饭填满,像没吃一样, 填满的动作就是 revert。 看上去桌上的饭没有动过,但是事情其实发生了,就是历史没有被改变。
  2. merge 的 revert:桌上有一碗饭,A 加了一道菜, B 加了一碗汤, merge 之后就是有饭有菜有汤。 现在在有饭有菜有汤的情况下去掉汤, 就是 git revert "commit of merge" -m 菜

8-02-工作琐事

Xpath // Trap

I am trying to loacte a attr element in a label context using capybara. The html looks like:

1
2
3
4
5
6
7
8
9
...
<div class="post">
<label for="post_name">
Name<attr title="required">*</attr>
</label>
</div>
...

And I use the code like:

1
2
3
4
5
6
within "div.post"
label = page.find "label", "Name"
label.find :xpath, "//attr[@title=\"required\"]"
end

As usual, I’m pretty sure that the find will work like:

1
//label//attr

the find search the xpath in label context and find it.

Quite easy but I’m wrong.

In XPath the expression // means something very specific, and it might not be what you think.
Contrary to common belief, // means anywhere in the document not anywhere in the current context.
As an example:

1
page.find(:xpath, '//body').all(:xpath, '//script')

You might expect this to find all script tags in the body,
but actually, it finds all script tags in the entire document,
not only those in the body! What you’re looking for is the .// expression which means “any descendant of the current node”:

1
page.find(:xpath, '//body').all(:xpath, './/script')

The same thing goes for within:

1
2
3
4
5
6
within(:xpath, '//body') do
page.find(:xpath, './/script')
within(:xpath, './/table/tbody') do
...
end
end

You can refer to capybara document

And also there is a great post: Using “//“ And “.//“ Expressions In XPath XML Search Directives In ColdFusion

|