Coverage

87%
397
349
48

browser.js

87%
291
254
37
LineHitsSource
1/**
2* This class is used for control browser.
3* <p>
4* Browser类用来实例化一个对象,以绑定目标浏览器并调用接口进行控制。
5* </p>
6* @class Browser
7*/
8
91var http = require('http'),
10 fs = require('fs'),
11 Fiber = require('fibers'),
12 extend = require('xtend');
13
141var Element = require('./element');
15
161var arrCommands = require('./commands.js');
17
18//颜色列表
191var colors = {
20 black: '\x1b[0;30m',
21 dkgray: '\x1b[1;30m',
22 brick: '\x1b[0;31m',
23 red: '\x1b[1;31m',
24 green: '\x1b[0;32m',
25 lime: '\x1b[1;32m',
26 brown: '\x1b[0;33m',
27 yellow: '\x1b[1;33m',
28 navy: '\x1b[0;34m',
29 blue: '\x1b[1;34m',
30 violet: '\x1b[0;35m',
31 magenta: '\x1b[1;35m',
32 teal: '\x1b[0;36m',
33 cyan: '\x1b[1;36m',
34 ltgray: '\x1b[0;37m',
35 white: '\x1b[1;37m',
36 reset: '\x1b[0m'
37};
38
39//响应代码
401var responseCodes = {
41 '0': {type: 'Success', message: 'The command executed successfully.'},
42 '7': {type: 'NoSuchElement', message: 'An element could not be located on the page using the given search parameters.'},
43 '8': {type: 'NoSuchFrame', message: 'A request to switch to a frame could not be satisfied because the frame could not be found.'},
44 '9': {type: 'UnknownCommand', message: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.'},
45 '10': {type: 'StaleElementReference', message: 'An element command failed because the referenced element is no longer attached to the DOM.'},
46 '11': {type: 'ElementNotVisible', message: 'An element command could not be completed because the element is not visible on the page.'},
47 '12': {type: 'InvalidElementState', message: 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).'},
48 '13': {type: 'UnknownError', message: 'An unknown server-side error occurred while processing the command.'},
49 '15': {type: 'ElementIsNotSelectable', message: 'An attempt was made to select an element that cannot be selected.'},
50 '17': {type: 'JavaScriptError', message: 'An error occurred while executing user supplied JavaScript.'},
51 '19': {type: 'XPathLookupError', message: 'An error occurred while searching for an element by XPath.'},
52 '23': {type: 'NoSuchWindow', message: 'A request to switch to a different window could not be satisfied because the window could not be found.'},
53 '24': {type: 'InvalidCookieDomain', message: 'An illegal attempt was made to set a cookie under a different domain than the current page.'},
54 '25': {type: 'UnableToSetCookie', message: 'A request to set a cookie\'s value could not be satisfied.'},
55 '26': {type: 'UnexpectedAlertOpen', message: 'A modal dia` was open, blocking this operation'},
56 '27': {type: 'NoAlertOpenError', message: 'An attempt was made to operate on a modal dialog when one was not open.'},
57 '28': {type: 'ScriptTimeout', message: 'A script did not complete before its timeout expired.'},
58 '29': {type: 'InvalidElementCoordinates', message: 'The coordinates provided to an interactions operation are invalid.'},
59 '30': {type: 'IMENotAvailable', message: 'IME was not available.'},
60 '31': {type : 'IMEEngineActivationFailed', message: 'An IME engine could not be started.'},
61 '32': {type: 'InvalidSelector', message: 'Argument was an invalid selector (e.g. XPath/CSS).'}
62};
63
64//默认浏览器参数
651var defaultOptions = {
66 'browserName': 'firefox',
67 'version': '',
68 'platform': 'ANY'
69}
70
71//浏览器昵称
721var arrBrowserNickName = {
73 'ie': 'internet explorer',
74 'ff': 'firefox'
75}
76
77//鼠标按钮映射表
781var arrMouseButton = {
79 'left': 0,
80 'middle': 1,
81 'right': 2
82}
83
84//键盘特殊按键映射表
851var arrSpecKeys = {
86 'null': '\uE000',
87 'cancel': '\uE001',
88 'help': '\uE002',
89 'back': '\uE003',
90 'tab': '\uE004',
91 'clear': '\uE005',
92 'return': '\uE006',
93 'enter': '\uE007',
94 'shift': '\uE008',
95 'ctrl': '\uE009',
96 'alt': '\uE00A',
97 'pause': '\uE00B',
98 'esc': '\uE00C',
99 'space': '\uE00D',
100 'pageup': '\uE00E',
101 'pagedown': '\uE00F',
102 'end': '\uE010',
103 'home': '\uE011',
104 'left': '\uE012',
105 'up': '\uE013',
106 'right': '\uE014',
107 'down': '\uE015',
108 'insert': '\uE016',
109 'delete': '\uE017',
110 'semicolon': '\uE018',
111 'equals': '\uE019',
112 'num0': '\uE01A',
113 'num1': '\uE01B',
114 'num2': '\uE01C',
115 'num3': '\uE01D',
116 'num4': '\uE01E',
117 'num5': '\uE01F',
118 'num6': '\uE020',
119 'num7': '\uE021',
120 'num8': '\uE022',
121 'num9': '\uE023',
122 'multiply': '\uE024',
123 'add': '\uE025',
124 'separator': '\uE026',
125 'subtract': '\uE027',
126 'decimal': '\uE028',
127 'divide': '\uE029',
128 'f1': '\uE031',
129 'f2': '\uE032',
130 'f3': '\uE033',
131 'f4': '\uE034',
132 'f5': '\uE035',
133 'f6': '\uE036',
134 'f7': '\uE037',
135 'f8': '\uE038',
136 'f9': '\uE039',
137 'f10': '\uE03A',
138 'f11': '\uE03B',
139 'f12': '\uE03C',
140 'command': '\uE03D',
141 'meta': '\uE03D'
142}
143
1441var Browser = function(){
1454 var self = this;
1464 return self._init.apply(self, arguments);
147}
148
1491Browser.prototype = {
150
151 /**
152 * 初始化浏览器对象
153 * @method _init
154 * @private
155 * @param {Object} config jWebDriver config
156 * @param {Object} options 浏览器初始化参数
157 * @param {Function} callback 回调函数
158 */
159 _init: function(config, options, callback){
1604 var self = this;
1614 self.sessionId = null;
1624 self.logMode = config.logMode;
1634 self.host = config.host;
1644 self.port = config.port;
1654 var browserName = options.browserName;
1664 if(browserName && (browserName = arrBrowserNickName[browserName])){
1670 options.browserName = browserName;
168 }
1694 var sessionOptions = {
170 'desiredCapabilities': extend({}, defaultOptions, options)
171 };
1724 if(options.Fiber){
1730 Fiber = options.Fiber;
1740 var result = self.doCommand('setSession', sessionOptions);
1750 callback(result);
176 }
177 else{
1784 self.doCommand('setSession', sessionOptions, callback);
179 }
1804 self.windowHandle = 'current';
181 },
182
183 /**
184 * 输出日志
185 * @method log
186 * @public
187 * @param {String} type 日志类型
188 * @param {String} message 日志内容
189 * @return {Browser} 当前Browser对象实例
190 */
191 log: function(type, message){
192738 var self = this;
193738 if(message === undefined){
1940 message = type;
1950 type = 'INFO';
196 }
197738 if(self.logMode === 'all' || (self.logMode === 'error' && type === 'ERROR')){
1980 var dateString = (new Date()).toString().match(/\d\d:\d\d:\d\d/)[0];
1990 var mapColors = {
200 'COMMAND': colors.violet,
201 'DATA': colors.brown,
202 'RESULT': colors.teal,
203 'ERROR': colors.red,
204 'WARNING': colors.yellow,
205 'INFO': colors.white
206 }
2070 if(message){
2080 console.log(colors.dkgray +'[' + dateString + ']: ' + colors.reset, mapColors[type] + type + colors.reset, '\t', message);
209 }
210 else{
2110 console.log(colors.dkgray +'[' + dateString + ']: ' + colors.reset, mapColors[type] + type + colors.reset);
212 }
213 }
214738 return self;
215 },
216
217 /**
218 * 检查返回值是否有错误,适用于所有WebDriver API的返回值
219 * @method isError
220 * @public
221 * @param {Object} result 所有WebDriver API的返回值
222 * @return {Boolean} 如果有错误返回true
223 */
224 isError: function(result){
225196 var status = result && result.status;
226196 return status !== undefined && status !== 0;
227 },
228
229 /**
230 * 检查返回值是否正确,适用于所有WebDriver API的返回值
231 * @method isOk
232 * @public
233 * @param {Object} result 所有WebDriver API的返回值
234 * @return {Boolean} 如果正确返回true
235 */
236 isOk: function(result){
237104 return !this.isError(result);
238 },
239
240 /**
241 * 执行webdriver命令
242 * @method doCommand
243 * @public
244 * @param {String} cmd 命令名称
245 * @param {Object} data 命令数据
246 * @param {Function} [callback] 回调函数,如果省略此参数,则为同步模式,直接返回结果
247 * @return {Object} webdriver返回的JSON对象
248 */
249 doCommand: function(cmd, data, callback){
250334 var self = this, cmdInfo = arrCommands[cmd];
251334 if(cmdInfo){
252334 var method = cmdInfo[0], path = cmdInfo[1];
253334 path = path.replace(':sessionId', self.sessionId);
254334 var pathValues;
255334 if(data && (pathValues = data.pathValues)){
256 //填充PATH中除sessionId以外的参数
257118 for(var name in pathValues){
258132 path = path.replace(':'+name, encodeURIComponent(pathValues[name]));
259 }
260118 delete data.pathValues;
261 }
262334 var requestOptions = {
263 'method': method,
264 'host': self.host,
265 'port': self.port,
266 'path': '/wd/hub'+path
267 };
268
269334 function getResult(result){
270334 if(result){
271224 result = result.replace(/\x00/g,'')
272224 try{
273224 result = JSON.parse(result);
274 }
275 catch(err){
2760 if (result !== '') {
2770 self.log('ERROR', err + '\n' + result + '\n')
278 }
2790 result = {status: -1, errorType: 'jsonError', errorMessage: 'JSON.parse error.'};
2800 return callback?callback(result):result;
281 }
282
283224 if (result.status === 0) {//操作成功
284210 if(cmd === 'setSession'){
2854 self.sessionId = result.sessionId;
2864 self.log('RESULT', self.sessionId);
2874 result = '';
288 }
289 else{
290206 self.log('RESULT', result.value);
291206 result = result.value;
292 }
293 }
294 else {
29514 var responseInfo = responseCodes[result.status],
296 responseType = responseInfo.type,
297 responseMessage = responseInfo.message;
298
29914 result.errorType = responseType;
30014 result.errorMessage = responseMessage;
301
30214 self.log('ERROR', responseType);
303 }
304 }
305334 if(callback){
30614 callback(result);
307 }
308 else{
309320 return result;
310 }
311 }
312334 if(callback){
31314 self._doRequest(requestOptions, data, getResult);
314 }
315 else{
316320 var response = self._doRequest(requestOptions, data);
317320 return getResult(response);
318 }
319 }
320 },
321
322 /**
323 * 发起HTTP请求到webdriver的接口
324 * @method _doRequest
325 * @private
326 * @param {Object} requestOptions HTTP请求参数
327 * @param {Object} data HTTP BODY
328 * @param {Function} [callback] 回调函数,活力回调函数则为同步返回模式
329 * @return {String} HTTP响应字符串
330 */
331 _doRequest: function(requestOptions, data, callback){
332334 var self = this, fiber = Fiber.current;
333
334334 self.log('COMMAND', requestOptions.method + '\t' + requestOptions.path);
335
336334 var responseStr = '';
337
338334 if(data){
339276 data = JSON.stringify(data);
340 //转义Unicode字符
341276 data = data.replace(/[^\x00-\xff]/g, function(a){
3424 return '\\'+escape(a).substr(1);
343 });
344276 if(data !== '{}'){
345178 self.log('DATA', data);
346 }
347 }
348 else{
34958 data = '';
350 }
351334 requestOptions.headers = {
352 'Accept': 'application/json; charset=utf-8',
353 'Content-Type': 'application/json;charset=UTF-8',
354 'Content-Length': data.length
355 }
356334 requestOptions.agent = false;
357
358334 var req = http.request(requestOptions, function(res){
359334 if ( /^302|303$/.test(res.statusCode) && self.sessionId === null) {
3600 try{
3610 var match = res.headers.location.match(/wd\/hub\/session\/(.+)$/i);
3620 if(match !== null){
3630 self.sessionId = match[1];
3640 self.log('RESULT', self.sessionId);
365 }
366 }
367 catch(e){
368 }
3690 return doCallback('');
370 }
371334 var arrResBuffers = [], resBufferSize = 0;
372334 res.on('data', function (data) {
373942 arrResBuffers.push(data);
374942 resBufferSize += data.length;
375 });
376334 res.on('end', function () {
377334 var resBuffer = new Buffer(resBufferSize), pos = 0;
378334 for(var i = 0, c = arrResBuffers.length; i < c; i++) {
379942 arrResBuffers[i].copy(resBuffer, pos);
380942 pos += arrResBuffers[i].length;
381 }
382334 responseStr = resBuffer.toString('utf-8');
383334 doCallback(responseStr);
384 });
385 });
386
387334 req.on('error', function(err)
388 {
3890 self.log('ERROR', 'ERROR ON REQUEST');
390 });
391
392334 if(data){
393276 req.write(data);
394 }
395334 req.end();
396
397334 function doCallback(data){
398334 if(callback){//异步模式
39914 callback(data);
400 }
401320 else if(fiber){//同步模式
402320 fiber.run();
403 }
404 }
405
406334 if(callback === undefined && fiber){
407320 Fiber.yield();
408320 return responseStr;
409 }
410 },
411
412 /**
413 * 延迟一定时间
414 * @method sleep
415 * @public
416 * @param {Number} ms 需要延迟的时间,单位毫秒
417 * @return {Browser} 当前Browser对象实例
418 */
419 sleep: function(ms){
42020 var fiber = Fiber.current;
42120 setTimeout(function(){
42220 fiber.run();
423 },ms);
42420 Fiber.yield();
42520 return this;
426 },
427
428 /**
429 * 返回窗口句柄
430 * @method window
431 * @public
432 * @param {Boolean} [bAll] 是否返回所有窗口,设置为true返回所有窗口
433 * @return {String|Array} 单个或数组形式的窗口句柄
434 */
435 window: function(bAll){
4366 return this.doCommand(bAll === true?'getWindows':'getWindow');
437 },
438
439 /**
440 * 切换到另一个Window或Frame
441 * @method switchTo
442 * @public
443 * @param {String|Element|Number} [target] 要切换的目标窗口句柄、Frame对象(Element)、Frame在页面中的序号(Number),如果省略此参数则切换到主窗口
444 * @return {Browser} 当前Browser对象实例
445 */
446 switchTo: function(target){
4478 var self = this;
4488 if(typeof target === 'string'){
4494 self.windowHandle = target;
4504 self.doCommand('switchWindow',{name: target});
451 }
4524 else if(target instanceof Element){
4532 self.doCommand('switchFrame',{id: target.toArray()});
454 }
4552 else if(target === undefined){
4562 self.doCommand('switchFrame',{id: null});
457 }
4588 return self;
459 },
460
461 /**
462 * 返回或者设置当前窗口的大小
463 * @method size
464 * @public
465 * @param {Number|Object} [width] 宽度或者大小对象({width,height})
466 * @param {Number} [height] 高度
467 * @return {Object|Browser} 如果无宽度和高度参数,则返回当前大小对象,否则返回当前Browser实例
468 */
469 size: function(width, height){
47014 var self = this,
471 windowHandle = self.windowHandle;
47214 if(width === undefined){
473 //返回首个窗口的大小
4748 return self.doCommand('getWindowSize', {'pathValues': {'windowHandle': windowHandle}});
475 }
4766 else if(width.width !== undefined){
4772 height = width.height;
4782 width = width.width;
479 }
4806 self.doCommand('setWindowSize', {
481 'pathValues': {'windowHandle': windowHandle},
482 'width': width,
483 'height': height
484 });
4856 return self;
486 },
487
488 /**
489 * 最大化当前窗口
490 * @method maximize
491 * @public
492 * @return {Browser} 当前Browser对象实例
493 */
494 maximize: function(){
4952 var self = this;
4962 self.doCommand('maximizeWindow', {'pathValues': {'windowHandle': self.windowHandle}});
4972 return self;
498 },
499
500 /**
501 * 返回或者设置当前窗口的坐标
502 * @method offset
503 * @public
504 * @param {Number|Object} [x] x坐标或者坐标对象({x,y})
505 * @param {Number} [y] y坐标
506 * @return {Object|Browser} 如果无x坐标和y坐标参数,则返回当前坐标对象,否则返回当前Browser实例
507 */
508 offset: function(x, y){
5098 var self = this,
510 windowHandle = self.windowHandle;
5118 if(x === undefined){
512 //返回首个窗口的大小
5134 return self.doCommand('getWindowOffset', {'pathValues': {'windowHandle': windowHandle}});
514 }
5154 else if(x.x !== undefined){
5162 y = x.y;
5172 x = x.x;
518 }
5194 self.doCommand('setWindowOffset', {
520 'pathValues': {'windowHandle': windowHandle},
521 'x': x,
522 'y': y
523 });
5244 return self;
525 },
526
527 /**
528 * 关闭当前窗口
529 * @method close
530 * @public
531 * @return {Browser} 当前Browser对象实例
532 */
533 close: function(){
5346 var self = this;
5356 self.doCommand('closeWindow');
5366 return self;
537 },
538
539 /**
540 * 结束浏览器会话
541 * @method end
542 * @public
543 * @return {Browser} 当前Browser对象实例
544 */
545 end: function(){
5460 var self = this;
5470 self.doCommand('delSession');
5480 return self;
549 },
550
551 /**
552 * 设置操作超时时间
553 * <p>
554 * 注:script包括同步和异步两种代码
555 * </p>
556 * @method setTimeout
557 * @public
558 * @param {String} type 操作类型(script|ascript|implicit|page load)
559 * @param {Number} ms 超时时间
560 * @return {Browser} 当前Browser对象实例
561 */
562 setTimeout: function(type, ms){
5634 var self = this;
5644 if(type === 'ascript'){
5654 self.doCommand('setAscriptTimeout', {'ms': ms});
566 }
567 else{
5680 self.doCommand('setTimeouts', {'type': type, 'ms': ms});
569 }
5704 return self;
571 },
572
573 /**
574 * 打开或者返回当前URL
575 * @method url
576 * @public
577 * @param {String} [url] 需要打开的URL网址,若省略此参数则返回当前浏览器URL
578 * @return {String|Browser} 若打开URL则返回当前Browser对象实例,否则返回URL地址
579 */
580 url: function(url){
58124 var self = this;
58224 if(url){
58314 self.doCommand('setUrl', {'url': url});
58414 return self;
585 }
586 else {
58710 return self.doCommand('getUrl');
588 }
589 },
590
591 /**
592 * 控制浏览器回到后一个URL
593 * @method forward
594 * @public
595 * @return {Browser} 当前Browser对象实例
596 */
597 forward: function(){
5982 var self = this;
5992 self.doCommand('setForward');
6002 return self;
601 },
602
603 /**
604 * 控制浏览器回到前一个URL
605 * @method back
606 * @public
607 * @return {Browser} 当前Browser对象实例
608 */
609 back: function(){
6102 var self = this;
6112 self.doCommand('setBack');
6122 return self;
613 },
614
615 /**
616 * 刷新当前页面
617 * @method refresh
618 * @public
619 * @return {Browser} 当前Browser对象实例
620 */
621 refresh: function(){
6222 var self = this;
6232 self.doCommand('setRefresh');
6242 return self;
625 },
626
627 /**
628 * 返回当前页面title
629 * @method title
630 * @public
631 * @return {String} 页面title
632 */
633 title: function(){
6342 return this.doCommand('getTitle');
635 },
636
637 /**
638 * 返回当前页面源代码
639 * @method source
640 * @public
641 * @return {String} 页面源代码
642 */
643 source: function(){
6442 return this.doCommand('getSource');
645 },
646
647 /**
648 * 返回当前页面下所有cookie
649 * @method getCookies
650 * @public
651 * @return {Array} cookie对象数组
652 */
653 getCookies: function(){
65410 return this.doCommand('getAllCookie');
655 },
656
657 /**
658 * 添加cookie到当前页面
659 * <p>
660 * 这里可以查看WebDriver官方定义的<a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object" target="_blank">cookie对象</a>
661 * </p>
662 * @method addCookie
663 * @public
664 * @param {Object} cookie cookie对象
665 * @return {Browser} 当前Browser对象实例
666 */
667 addCookie: function(cookie){
6684 var self = this;
6694 self.doCommand('setCookie', {'cookie': cookie});
6704 return self;
671 },
672
673 /**
674 * 删除cookie
675 * @method delCookies
676 * @public
677 * @param {String} name cookie name
678 * @return {Browser} 当前Browser对象实例
679 */
680 delCookies: function(name){
6814 var self = this;
6824 if(name !== undefined){
6832 self.doCommand('delCookie', {'pathValues': {'name': name}});
684 }
685 else{
6862 self.doCommand('delAllCookies');
687 }
6884 return self;
689 },
690
691 /**
692 * 执行Javascript脚本
693 * @method exec
694 * @public
695 * @param {String} script Javascript代码
696 * @param {Array|Object} [args] 传递给脚本的参数,此参数可省略
697 * @param {Boolean} bAsync 是否异步,如果是异步必需要调用callback返回,否则一直阻塞
698 * @return {Object} Javascript脚本的返回值
699 */
700 exec: function(script, args, bAsync){
70124 if(typeof args === 'boolean'){
7024 bAsync = args;
7034 args = [];
704 }
70524 args = args?args:[];
70624 if(Object.prototype.toString.apply(args) !== '[object Array]'){
707 //单参数模式
7086 args = [args];
709 }
71024 var arg;
71124 for(var i in args){
71214 arg = args[i];
71314 if(arg instanceof Element){
714 //转换为原生Element对象
7152 args[i] = arg.toArray();
716 }
717 }
71824 var data = {'script': script, args: args};
71924 return this.doCommand(bAsync === true?'executeAsync':'execute', data);
720 },
721
722 /**
723 * 获取弹出窗口的文本(alert, confirm, prompt)
724 * @method getAlert
725 * @public
726 * @return {String} 弹出窗口的文本
727 */
728 getAlert: function(){
7292 return this.doCommand('getAlert');
730 },
731
732 /**
733 * 输入prompt中的值
734 * @method setAlert
735 * @public
736 * @return {Browser} 当前Browser对象实例
737 */
738 setAlert: function(str){
7390 var self = this;
7400 self.doCommand('setAlert', {'text': str});
7410 return self;
742 },
743
744 /**
745 * 关闭弹出窗口
746 * @method closeAlert
747 * @public
748 * @param {String} [btn] 点击哪个按钮关闭窗口:ok|cancel,默认cancel
749 * @return {Browser} 当前Browser对象实例
750 */
751 closeAlert: function(btn){
7522 var self = this;
7532 self.doCommand(/^ok$/i.test(btn)?'acceptAlert':'dismissAlert');
7542 return self;
755 },
756
757 /**
758 * 获取或保存当前网页截图
759 * <p>
760 * filePath为可选项,若有则保存到文件
761 * </p>
762 * @method getScreenshot
763 * @public
764 * @param {String} [filePath] 保存截图的文件路径
765 * @return {String} Base64格式的网页截图
766 */
767 getScreenshot: function(filePath){
7682 var data = this.doCommand('getScreenshot');
7692 if(filePath){
7702 fs.writeFileSync(filePath, data, "base64");
771 }
7722 return data;
773 },
774
775 /**
776 * 获取按键映射后的字符数组
777 * @method _getKeyArray
778 * @private
779 * @param {String} str 原始字符串
780 * @return {Array} 经过映射后的字符数组
781 */
782 _getKeyArray: function(str){
78314 str = str.replace(/{(\w+)}/g, function(all, name){
7844 var key = arrSpecKeys[name.toLowerCase()];
7854 return key?key:all;
786 });
78714 return str.split('');
788 },
789
790 /**
791 * 发送按键序列到当前焦点对象上
792 * @method sendKeys
793 * @public
794 * @param {String} str 原始字符串
795 * @return {Browser} 当前Browser对象实例
796 */
797 sendKeys: function(str){
7982 var self = this;
7992 self.doCommand('sendKeys', {'value': self._getKeyArray(str)});
8002 return self;
801 },
802
803 /**
804 * 在当前光标处触发单击事件
805 * @method click
806 * @public
807 * @param {String} mouseBtn 鼠标按钮类型:left|middle|right
808 * @return {Browser} 当前Browser对象实例
809 */
810 click: function(mouseBtn){
8114 var self = this, data = {};
8124 if(mouseBtn){
8130 var button = arrMouseButton[mouseBtn.toLowerCase()];
8140 if(button){
8150 data.button = button;
816 }
817 }
8184 self.doCommand('click', data);
8194 return self;
820 },
821
822 /**
823 * 在当前光标处触发双击事件
824 * @method dblclick
825 * @public
826 * @return {Browser} 当前Browser对象实例
827 */
828 dblclick: function(){
8292 var self = this;
8302 self.doCommand('dblclick');
8312 return self;
832 },
833
834 /**
835 * 在当前光标处触发鼠标左键按下
836 * @method mousedown
837 * @public
838 * @return {Browser} 当前Browser对象实例
839 */
840 mousedown: function(){
8412 var self = this;
8422 self.doCommand('mousedown');
8432 return self;
844 },
845
846 /**
847 * 移动到指定对象中间或坐标点
848 * @method mousemove
849 * @public
850 * @param {Number|Element|Object} x X坐标或Element对象或{x,y}
851 * @param {Number} y y坐标
852 * @return {Browser} 当前Browser对象实例
853 */
854 mousemove: function(x, y){
8556 var self = this, data = {};
8566 if(x instanceof Element){
8574 data.element = x.toArray().ELEMENT;
858 }
8592 else if(x.x && x.y){
8600 data.xoffset = x.x;
8610 data.yoffset = x.y;
862 }
863 else{
8642 data.xoffset = x;
8652 data.yoffset = y;
866 }
8676 if(data.element === undefined){
8682 data.xoffset = Math.round(data.xoffset);
8692 data.yoffset = Math.round(data.yoffset);
870 }
8716 self.doCommand('mousemove', data);
8726 return self;
873 },
874
875 /**
876 * 在当前光标处触发鼠标左键按起
877 * @method mouseup
878 * @public
879 * @return {Browser} 当前Browser对象实例
880 */
881 mouseup: function(){
8822 var self = this;
8832 self.doCommand('mouseup');
8842 return self;
885 },
886
887 /**
888 * 拖放
889 * @method dragDrop
890 * @public
891 * @param {Element|Object} from 源对象或源坐标({x,y})
892 * @param {Element|Object} to 目标对象或目标坐标({x,y})
893 * @return {Browser} 当前Browser对象实例
894 */
895 dragDrop: function(from, to){
8960 var self = this;
8970 self.mousemove(from).mousedown().mousemove(to).mouseup();
8980 return self;
899 },
900
901 /**
902 * 等待对象出现或消失
903 * @method waitFor
904 * @public
905 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
906 * @param {String} value 对象选择值
907 * @param {Boolean} [targetExist] 测试目标是对象存在或不存在
908 * @param {Number} [timeout] 等待超时时间,单位毫秒,默认30000毫秒
909 * @return {Element|Object} 等待的目标如果存在,则返回Element对象,如果不存在或者超时则返回错误对象
910 */
911 waitFor: function(using, value, targetExist, timeout){
9126 var self = this,
913 fiber = Fiber.current,
914 ret, bExist, bTimeout = false;
9156 if(typeof value !== 'string'){
916 //默认css选择器
9176 timeout = targetExist;
9186 targetExist = value;
9196 value = using;
9206 using = 'css selector';
921 }
9226 if(typeof targetExist === 'number'){
9232 timeout = targetExist;
9242 targetExist = true;
925 }
9266 if(targetExist === undefined){
9272 targetExist = true;
928 }
9296 if(timeout === undefined){
9304 timeout = 30000;
931 }
9326 var _timer1, _timer2;
9336 function waitElement(){
93410 self.doCommand('getElement', {'using': using,'value': value}, function(result){
93510 bExist = self.isOk(result);
93610 ret = result;
93710 if(bExist === targetExist){
9384 clearTimeout(_timer2);
9394 fiber.run();
940 }
9416 else if(bTimeout === true){
9422 fiber.run();
943 }
9444 else if(_timer1 !== null){
945 //每隔500毫秒确认目标是否存在
9464 _timer1 = setTimeout(waitElement, 500);
947 }
948 });
949 }
9506 _timer1 = setTimeout(waitElement, 1);
9516 _timer2 = setTimeout(function(){
9522 self.log('ERROR', 'waitFor timeout: ' + using + ' , ' + value);
953 //标记超时,getElement返回时结束当前等待请求
9542 bTimeout = true;
955 }, timeout);
9566 Fiber.yield();
9576 return bExist === true ? self.element('element', ret.ELEMENT) : ret;
958 },
959
960 /**
961 * 返回Element对象实例
962 * <p>
963 * 此方法不建议直接调用,建议使用run函数的第二个参数$符,例如:
964 * <pre>
965 * wd.run(function(browser, $){
966 * $('#id').val('test');
967 * })
968 * </pre>
969 * </p>
970 * @method element
971 * @public
972 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
973 * @param {String} [value] 对象选择值,留空返回当前焦点所在对象
974 * @return {Element} Element的实例对象,如果对象不存在,返回原始消息
975 */
976 element: function(using, value){
97782 var self = this;
97882 var element = new Element(self, using, value);
97982 return self.isError(element._id) ? element._id : element;
980 }
981
982}
983
9841module.exports = Browser;

commands.js

100%
2
2
0
LineHitsSource
1
2// http://code.google.com/p/selenium/wiki/JsonWireProtocol#Command_Reference
3
41var arrCommands = {
5 //session
6 'setSession' : ['POST', '/session'],
7 'delSession' : ['DELETE', '/session/:sessionId'],
8
9 //window & frame
10 'getWindow' : ['GET', '/session/:sessionId/window_handle'],
11 'getWindows' : ['GET', '/session/:sessionId/window_handles'],
12 'switchWindow' : ['POST', '/session/:sessionId/window'],
13 'closeWindow' : ['DELETE', '/session/:sessionId/window'],
14 'getWindowSize' : ['GET', '/session/:sessionId/window/:windowHandle/size'],
15 'setWindowSize' : ['POST', '/session/:sessionId/window/:windowHandle/size'],
16 'maximizeWindow' : ['POST', '/session/:sessionId/window/:windowHandle/maximize'],
17 'getWindowOffset' : ['GET', '/session/:sessionId/window/:windowHandle/position'],
18 'setWindowOffset' : ['POST', '/session/:sessionId/window/:windowHandle/position'],
19 'switchFrame' : ['POST', '/session/:sessionId/frame'],
20
21 //browser
22 'setTimeouts' : ['POST', '/session/:sessionId/timeouts'],
23 'setAscriptTimeout' : ['POST', '/session/:sessionId/timeouts/async_script'],
24
25 'setUrl' : ['POST', '/session/:sessionId/url'],
26 'getUrl' : ['GET', '/session/:sessionId/url'],
27 'setForward' : ['POST', '/session/:sessionId/forward'],
28 'setBack' : ['POST', '/session/:sessionId/back'],
29 'setRefresh' : ['POST', '/session/:sessionId/refresh'],
30
31 'getTitle' : ['GET', '/session/:sessionId/title'],
32 'getSource' : ['GET', '/session/:sessionId/source'],
33
34 'getAllCookie' : ['GET', '/session/:sessionId/cookie'],
35 'setCookie' : ['POST', '/session/:sessionId/cookie'],
36 'delAllCookies' : ['DELETE', '/session/:sessionId/cookie'],
37 'delCookie' : ['DELETE', '/session/:sessionId/cookie/:name'],
38
39 'execute' : ['POST', '/session/:sessionId/execute'],
40 'executeAsync' : ['POST', '/session/:sessionId/execute_async'],
41
42 'getAlert' : ['GET', '/session/:sessionId/alert_text'],
43 'setAlert' : ['POST', '/session/:sessionId/alert_text'],
44 'acceptAlert' : ['POST', '/session/:sessionId/accept_alert'],
45 'dismissAlert' : ['POST', '/session/:sessionId/dismiss_alert'],
46
47 'getScreenshot' : ['GET', '/session/:sessionId/screenshot'],
48
49 //key & mouse
50 'sendKeys' : ['POST', '/session/:sessionId/keys'],
51 'click' : ['POST', '/session/:sessionId/click'],
52 'dblclick' : ['POST', '/session/:sessionId/doubleclick'],
53 'mousedown' : ['POST', '/session/:sessionId/buttondown'],
54 'mousemove' : ['POST', '/session/:sessionId/moveto'],
55 'mouseup' : ['POST', '/session/:sessionId/buttonup'],
56
57 //element
58 'getActiveElement': ['POST', '/session/:sessionId/element/active'],
59 'getElement': ['POST', '/session/:sessionId/element'],
60 'getElements': ['POST', '/session/:sessionId/elements'],
61 'findElement': ['POST', '/session/:sessionId/element/:id/element'],
62
63 'getAttr': ['GET', '/session/:sessionId/element/:id/attribute/:name'],
64 'getCss': ['GET', '/session/:sessionId/element/:id/css/:propertyName'],
65 'getValue': ['GET', '/session/:sessionId/element/:id/value'],
66 'setValue': ['POST', '/session/:sessionId/element/:id/value'],
67 'setClear': ['POST', '/session/:sessionId/element/:id/clear'],
68 'getText': ['GET', '/session/:sessionId/element/:id/text'],
69
70 'getDisplayed': ['GET', '/session/:sessionId/element/:id/displayed'],
71 'getLocation': ['GET', '/session/:sessionId/element/:id/location'],
72 'getLocationInView': ['GET', '/session/:sessionId/element/:id/location_in_view'],
73 'getSize': ['GET', '/session/:sessionId/element/:id/size'],
74
75 'getEnabled': ['GET', '/session/:sessionId/element/:id/enabled'],
76 'getSelected': ['GET', '/session/:sessionId/element/:id/selected'],
77
78 'setSubmit': ['POST', '/session/:sessionId/element/:id/submit'],
79 'setClick': ['POST', '/session/:sessionId/element/:id/click'],
80
81 'getElementEquals': ['GET', '/session/:sessionId/element/:id/equals/:other']
82}
83
841module.exports = arrCommands;

element.js

96%
61
59
2
LineHitsSource
1/**
2* This class is used for control element.
3* <p>
4* 此类用来和页面对象进行交互。
5* </p>
6* @class Element
7*/
8
91var Element = function(){
1084 var self = this;
1184 return self._init.apply(self, arguments);
12}
13
141Element.prototype = {
15
16 /**
17 * 初始化Element对象
18 * @method _init
19 * @private
20 * @param {Browser} browser Element所在的Browser对象实例
21 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
22 * @param {String} [value] 对象选择值,留空返回当前焦点所在对象
23 */
24 _init: function(browser, using, value){
2584 var self = this;
2684 self.browser = browser;
2784 if(value === undefined){
28 //默认css选择器
2980 value = using;
3080 using = 'css selector';
31 }
3284 self.using = using;
3384 self.value = value;
3484 if(value === undefined){
352 self._id = browser.doCommand('getActiveElement').ELEMENT;
36 }
3782 else if(using === 'element'){
384 self._id = value;
39 }
40 else{
4178 var regCheck = /^(class name|css selector|id|name|link text|partial link text|tag name|xpath)$/i;
4278 if (regCheck.test(using) === false)
43 {
440 throw 'Please provide any of the following using strings as the first parameter: class name, css selector, id, name, link text, partial link text, tag name or xpath';
45 }
4678 var result = browser.doCommand('getElement', {'using': using,'value': value});
4778 self._id = browser.isOk(result) ? result.ELEMENT : result;
48 }
49 },
50
51 /**
52 * 在对应对象上执行Element命令
53 * @method _doElementCommand
54 * @private
55 * @param {String} cmd 命令
56 * @param {Object} data 命令需要的数据
57 * @return {Object} 命令执行结果
58 */
59 _doElementCommand: function(cmd, data){
6092 var self = this;
6192 data = data || {};
6292 data['pathValues'] = data['pathValues'] || {};
6392 data.pathValues.id = self._id;
6492 return self.browser.doCommand(cmd, data);
65 },
66
67 /**
68 * 返回官方数组格式的Element对象
69 * @method toArray
70 * @public
71 * @return {Object} 官方Element对象{ELEMENT}
72 */
73 toArray: function(){
748 return {ELEMENT: this._id};
75 },
76
77 /**
78 * 查找对象下的对象
79 * @method find
80 * @public
81 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
82 * @param {String} value 对象选择值
83 * @return {Element} Element的实例对象
84 */
85 find: function(using, value){
862 var self = this;
872 if(value === undefined){
88 //默认css选择器
892 value = using;
902 using = 'css selector';
91 }
922 var result = self._doElementCommand('findElement', {'using': using,'value': value});
932 if(self.browser.isOk(result)){
942 return new Element(self.browser, 'element', result.ELEMENT);
95 }
960 return result;
97 },
98
99 /**
100 * 返回DOM属性值
101 * @method attr
102 * @public
103 * @param {String} name 属性名称
104 * @return {String} 属性值
105 */
106 attr: function(name){
10710 return this._doElementCommand('getAttr', {'pathValues': {'name': name}});
108 },
109
110 /**
111 * 返回DOM的CSS属性值
112 * @method css
113 * @public
114 * @param {String} name CSS名称
115 * @return {String} CSS值
116 */
117 css: function(name){
1182 return this._doElementCommand('getCss', {'pathValues': {'propertyName': name}});
119 },
120
121 /**
122 * 返回或设置value值
123 * @method val
124 * @public
125 * @param {String} [str] value值,此值省略时为取value值
126 * @return {String|Element} 返回value值或者当前Element实例
127 */
128 val: function(str){
12930 var self = this;
13030 if(str){
13110 self.clear().sendKeys(str);
13210 return self;
133 }
134 else{
13520 return self._doElementCommand('getValue');
136 }
137 },
138
139 /**
140 * 清空value值
141 * @method clear
142 * @public
143 * @return {Element} 返回当前Element实例
144 */
145 clear: function(){
14612 var self = this;
14712 self._doElementCommand('setClear');
14812 return self;
149 },
150
151 /**
152 * 返回当前对象可视文本
153 * @method text
154 * @public
155 * @return {String} 返回的可视文本
156 */
157 text: function(){
1582 return this._doElementCommand('getText');
159 },
160
161 /**
162 * 当前对象是否可视
163 * @method visible
164 * @public
165 * @return {Boolean} 当前对象的可视状态
166 */
167 visible: function(){
1686 return this._doElementCommand('getDisplayed');
169 },
170
171 /**
172 * 当前对象的坐标
173 * @method offset
174 * @public
175 * @param {Boolean} bInView 是否相对坐标
176 * @return {Object} 返回{x,y}格式的对象
177 */
178 offset: function(bInView){
1794 return this._doElementCommand(bInView?'getLocationInView':'getLocation');
180 },
181
182 /**
183 * 当前对象的大小
184 * @method size
185 * @public
186 * @return {Object} 返回{width,height}格式的对象
187 */
188 size: function(){
1894 return this._doElementCommand('getSize');
190 },
191
192 /**
193 * 当前对象是否可用状态
194 * @method enabled
195 * @public
196 * @return {Boolean} 当前对象的可用状态
197 */
198 enabled: function(){
1994 return this._doElementCommand('getEnabled');
200 },
201
202 /**
203 * 当前对象是否处于被选择状态
204 * 适用于以下对象:option,checkbox,radio
205 * @method selected
206 * @public
207 * @return {Boolean} 当前对象是否被选择
208 */
209 selected: function(){
2104 return this._doElementCommand('getSelected');
211 },
212
213 /**
214 * 提前对象所在的表单
215 * @method submit
216 * @public
217 * @return {Element} 返回当前Element实例
218 */
219 submit: function(){
2202 var self = this;
2212 self._doElementCommand('setSubmit');
2222 return self;
223 },
224
225 /**
226 * 单击当前对象
227 * @method click
228 * @public
229 * @return {Element} 返回当前Element实例
230 */
231 click: function(){
2326 var self = this;
2336 self._doElementCommand('setClick');
2346 return self;
235 },
236
237 /**
238 * 发送键盘按键序列到当前对象上
239 * @method sendKeys
240 * @public
241 * @param {String} str 键盘按钮序列
242 * @return {Element} 返回当前Element实例
243 */
244 sendKeys: function(str){
24512 var self = this;
24612 self._doElementCommand('setValue', {'value': self.browser._getKeyArray(str)});
24712 return self;
248 },
249
250 /**
251 * 比较两个Element是否指向同一个DOM对象
252 * @method equals
253 * @public
254 * @param {Element} $element 需要比较的Element对象实例
255 * @return {Boolean} 是否指向同一个DOM对象
256 */
257 equals: function($element){
2582 return this._doElementCommand('getElementEquals', {'pathValues': {'other': $element._id}});
259 }
260}
261
2621module.exports = Element;

jwebdriver.js

79%
43
34
9
LineHitsSource
1/**
2 * A webdriver client for Node.js
3 * <p>
4 * 此对象是jwebdriver组件的入口,主要用来初始化及同步运行测试代码。
5 * </p>
6 * <pre>
7 * var JWebDriver = require('jwebdriver');
8 * JWebDriver.config({
9 * 'logMode': 'all',
10 * 'host': 'localhost',
11 * 'port': 4444
12 * });
13 * var wd = new JWebDriver({'browserName':'chrome'});
14 * wd.run(function(browser, $){
15 * browser.url("http://www.baidu.com/");
16 * var kw = $('#kw');
17 * kw.val('mp3').submit();
18 * browser.end();
19 * });
20 * </pre>
21 * @class JWebDriver
22 */
23
241var extend = require('xtend'),
25 Fiber = require('fibers');
26
271var Browser = require('./browser');
28
29//全局配置
301var wdConfig, defConfig = {
31 'logMode': 'all', //slient|error|all
32 'host': 'localhost',
33 'port': 4444
34};
351wdConfig = defConfig;
36
371var JWebDriver = function(){
384 var self = this;
394 return self._init.apply(self, arguments);
40}
41
42/**
43 * 设置全局配置
44 * <p>
45 * 设置JWebDriver全局配置,后续所有实例共享此配置
46 * </p>
47 * <pre>
48 * JWebDriver.config({
49 * 'logMode': 'all',
50 * 'host': 'localhost',
51 * 'port': 4444
52 * });
53 * </pre>
54 * @method config
55 * @static
56 * @public
57 * @param {Object} options 选项参数
58 */
591JWebDriver.config = function(options){
602 wdConfig = extend({}, defConfig, options);
61}
62
631JWebDriver.prototype = {
64
65 /**
66 * 初始化函数
67 * @method _init
68 * @private
69 * @param {Object} options 初始化选项
70 * @param {Function} onError 失败回调函数
71 */
72 _init: function(options, onError){
734 var self = this;
744 self._Fiber = options.Fiber;
754 self._arrTasks = [];//任务队列数组
764 self._bRun = true;//运行状态
774 self._browser = new Browser(wdConfig, options, function(result){
784 if(result && result.status !== 0){
790 if(onError){
800 onError(result);
81 }
82 }
83 else{
84 //Session初始化成功,执行任务队列
854 self._bRun = false;
864 self._run();
87 }
88 });
89 },
90
91 /**
92 * 加入任务队列
93 * <p>
94 * 若当前队列为空,则直接运行
95 * </p>
96 * @method run
97 * @public
98 * @param {Function} func 需要运行的函数
99 * @return {JWebDriver} 当前JWebDriver对象实例
100 */
101 run: function(func){
10276 var self = this;
103 //插入运行队列
10476 self._arrTasks.push(func);
10576 if(!self._bRun){
10672 self._run();
107 }
10876 return self;
109 },
110
111 /**
112 * 运行队列中的任务
113 * @private
114 * @method _run
115 */
116 _run: function(){
117152 var self = this ,
118 browser = self._browser,
119 func = self._arrTasks.shift();
120152 if(func && browser !== null){
12176 var taskFunc = function(){
12276 self._bRun = true;
12376 func(browser, function(){
12480 return browser.element.apply(browser, arguments);
125 });
12676 self._bRun = false;
127 //继续运行下个任务
12876 self._run();
129 };
13076 if(self._Fiber){
1310 taskFunc();
132 }
133 else{
13476 Fiber(taskFunc).run();
135 }
136 }
137 },
138
139 /**
140 * 结束WebDriver会话
141 * <p>
142 * 本接口仅能取消后续未执行的任务队列,已经在执行中的无法中断,并且会在当前任务执行完毕后才中断。
143 * </p>
144 * @method end
145 * @public
146 */
147 end: function(){
1480 var self = this;
1490 if(self._browser !== null){
1500 self._arrTasks = [];
1510 self.run(function(browser){
1520 browser.end();
1530 self._browser = null;
154 });
155 }
156 }
157}
158
1591module.exports = JWebDriver;