Coverage

90%
387
349
38

jwebdriver.js

80%
42
34
8
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 = 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 , _arrTasks = self._arrTasks;
118152 if(_arrTasks.length>0){
11976 var func = _arrTasks.shift();
120 self._Fiber(function(){
12176 self._bRun = true;
12276 var browser = self._browser;
12376 func(browser, function(){
12480 return browser.element.apply(browser, arguments);
125 });
12676 self._bRun = false;
127 //继续运行下个任务
12876 self._run();
12976 }).run();
130 }
131 },
132
133 /**
134 * 结束WebDriver会话
135 * <p>
136 * 本接口仅能取消后续未执行的任务队列,已经在执行中的无法中断,并且会在当前任务执行完毕后才中断。
137 * </p>
138 * @method end
139 * @public
140 */
141 end: function(){
1420 var self = this;
1430 if(self._browser !== null){
1440 self._arrTasks = [];
1450 self.run(function(browser){
1460 browser.end();
1470 self._browser = null;
148 });
149 }
150 }
151}
152
1531module.exports = JWebDriver;

browser.js

90%
282
254
28
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 self.doCommand('setSession', sessionOptions, callback);
1734 self.windowHandle = 'current';
174 },
175
176 /**
177 * 输出日志
178 * @method log
179 * @public
180 * @param {String} type 日志类型
181 * @param {String} message 日志内容
182 * @return {Browser} 当前Browser对象实例
183 */
184 log: function(type, message){
185738 var self = this;
186738 if(message === undefined){
1870 message = type;
1880 type = 'INFO';
189 }
190738 if(self.logMode === 'all' || (self.logMode === 'error' && type === 'ERROR')){
1910 var dateString = (new Date()).toString().match(/\d\d:\d\d:\d\d/)[0];
1920 var mapColors = {
193 'COMMAND': colors.violet,
194 'DATA': colors.brown,
195 'RESULT': colors.teal,
196 'ERROR': colors.red,
197 'WARNING': colors.yellow,
198 'INFO': colors.white
199 }
2000 if(message){
2010 console.log(colors.dkgray +'[' + dateString + ']: ' + colors.reset, mapColors[type] + type + colors.reset, '\t', message);
202 }
203 else{
2040 console.log(colors.dkgray +'[' + dateString + ']: ' + colors.reset, mapColors[type] + type + colors.reset);
205 }
206 }
207738 return self;
208 },
209
210 /**
211 * 检查返回值是否有错误,适用于所有WebDriver API的返回值
212 * @method isError
213 * @public
214 * @param {Object} result 所有WebDriver API的返回值
215 * @return {Boolean} 如果有错误返回true
216 */
217 isError: function(result){
218196 var status = result && result.status;
219196 return status !== undefined && status !== 0;
220 },
221
222 /**
223 * 检查返回值是否正确,适用于所有WebDriver API的返回值
224 * @method isOk
225 * @public
226 * @param {Object} result 所有WebDriver API的返回值
227 * @return {Boolean} 如果正确返回true
228 */
229 isOk: function(result){
230104 return !this.isError(result);
231 },
232
233 /**
234 * 执行webdriver命令
235 * @method doCommand
236 * @public
237 * @param {String} cmd 命令名称
238 * @param {Object} data 命令数据
239 * @param {Function} [callback] 回调函数,如果省略此参数,则为同步模式,直接返回结果
240 * @return {Object} webdriver返回的JSON对象
241 */
242 doCommand: function(cmd, data, callback){
243334 var self = this, cmdInfo = arrCommands[cmd];
244334 if(cmdInfo){
245334 var method = cmdInfo[0], path = cmdInfo[1];
246334 path = path.replace(':sessionId', self.sessionId);
247334 var pathValues;
248334 if(data && (pathValues = data.pathValues)){
249 //填充PATH中除sessionId以外的参数
250118 for(var name in pathValues){
251132 path = path.replace(':'+name, encodeURIComponent(pathValues[name]));
252 }
253118 delete data.pathValues;
254 }
255334 var requestOptions = {
256 'method': method,
257 'host': self.host,
258 'port': self.port,
259 'path': '/wd/hub'+path
260 };
261
262334 function getResult(result){
263334 if(result){
264220 result = result.replace(/\x00/g,'')
265220 try{
266220 result = JSON.parse(result);
267 }
268 catch(err){
2690 if (result !== '') {
2700 self.log('ERROR', err + '\n' + result + '\n')
271 }
2720 result = {status: -1, errorType: 'jsonError', errorMessage: 'JSON.parse error.'};
2730 return callback?callback(result):result;
274 }
275
276220 if (result.status === 0) {//操作成功
277206 self.log('RESULT', result.value);
278206 result = result.value;
279 }
280 else {
28114 var responseInfo = responseCodes[result.status],
282 responseType = responseInfo.type,
283 responseMessage = responseInfo.message;
284
28514 result.errorType = responseType;
28614 result.errorMessage = responseMessage;
287
28814 self.log('ERROR', responseType);
289 }
290 }
291334 if(callback){
29214 callback(result);
293 }
294 else{
295320 return result;
296 }
297 }
298334 if(callback){
29914 self._doRequest(requestOptions, data, getResult);
300 }
301 else{
302320 var response = self._doRequest(requestOptions, data);
303320 return getResult(response);
304 }
305 }
306 },
307
308 /**
309 * 发起HTTP请求到webdriver的接口
310 * @method _doRequest
311 * @private
312 * @param {Object} requestOptions HTTP请求参数
313 * @param {Object} data HTTP BODY
314 * @param {Function} [callback] 回调函数,活力回调函数则为同步返回模式
315 * @return {String} HTTP响应字符串
316 */
317 _doRequest: function(requestOptions, data, callback){
318334 var self = this, fiber = Fiber.current;
319
320334 self.log('COMMAND', requestOptions.method + '\t' + requestOptions.path);
321
322334 var responseStr = '';
323
324334 if(data){
325276 data = JSON.stringify(data);
326 //转义Unicode字符
327276 data = data.replace(/[^\x00-\xff]/g, function(a){
3284 return '\\'+escape(a).substr(1);
329 });
330276 if(data !== '{}'){
331178 self.log('DATA', data);
332 }
333 }
334 else{
33558 data = '';
336 }
337334 requestOptions.headers = {
338 'Accept': 'application/json; charset=utf-8',
339 'Content-Type': 'application/json;charset=UTF-8',
340 'Content-Length': data.length
341 }
342
343334 var req = http.request(requestOptions, function(res){
344334 if ( /^302|303$/.test(res.statusCode) && self.sessionId === null) {
3454 try{
3464 var match = res.headers.location.match(/wd\/hub\/session\/(.+)$/i);
3474 if(match !== null){
3484 self.sessionId = match[1];
3494 self.log('RESULT', self.sessionId);
350 }
351 }
352 catch(e){
353 }
3544 return doCallback('');
355 }
356330 var arrResBuffers = [], resBufferSize = 0;
357330 res.on('data', function (data) {
3581014 arrResBuffers.push(data);
3591014 resBufferSize += data.length;
360 });
361330 res.on('end', function () {
362330 var resBuffer = new Buffer(resBufferSize), pos = 0;
363330 for(var i = 0, c = arrResBuffers.length; i < c; i++) {
3641014 arrResBuffers[i].copy(resBuffer, pos);
3651014 pos += arrResBuffers[i].length;
366 }
367330 responseStr = resBuffer.toString('utf-8');
368330 doCallback(responseStr);
369 });
370 });
371
372334 req.on('error', function(err)
373 {
3740 self.log('ERROR', 'ERROR ON REQUEST');
375 });
376
377334 if(data){
378276 req.write(data);
379 }
380334 req.end();
381
382334 function doCallback(data){
383334 if(callback){//异步模式
38414 callback(data);
385 }
386320 else if(fiber){//同步模式
387320 fiber.run();
388 }
389 }
390
391334 if(callback === undefined && fiber){
392320 Fiber.yield();
393320 return responseStr;
394 }
395 },
396
397 /**
398 * 延迟一定时间
399 * @method sleep
400 * @public
401 * @param {Number} ms 需要延迟的时间,单位毫秒
402 * @return {Browser} 当前Browser对象实例
403 */
404 sleep: function(ms){
40520 var fiber = Fiber.current;
40620 setTimeout(function(){
40720 fiber.run();
408 },ms);
40920 Fiber.yield();
41020 return this;
411 },
412
413 /**
414 * 返回窗口句柄
415 * @method window
416 * @public
417 * @param {Boolean} [bAll] 是否返回所有窗口,设置为true返回所有窗口
418 * @return {String|Array} 单个或数组形式的窗口句柄
419 */
420 window: function(bAll){
4216 return this.doCommand(bAll === true?'getWindows':'getWindow');
422 },
423
424 /**
425 * 切换到另一个Window或Frame
426 * @method switchTo
427 * @public
428 * @param {String|Element|Number} [target] 要切换的目标窗口句柄、Frame对象(Element)、Frame在页面中的序号(Number),如果省略此参数则切换到主窗口
429 * @return {Browser} 当前Browser对象实例
430 */
431 switchTo: function(target){
4328 var self = this;
4338 if(typeof target === 'string'){
4344 self.windowHandle = target;
4354 self.doCommand('switchWindow',{name: target});
436 }
4374 else if(target instanceof Element){
4382 self.doCommand('switchFrame',{id: target.toArray()});
439 }
4402 else if(target === undefined){
4412 self.doCommand('switchFrame',{id: null});
442 }
4438 return self;
444 },
445
446 /**
447 * 返回或者设置当前窗口的大小
448 * @method size
449 * @public
450 * @param {Number|Object} [width] 宽度或者大小对象({width,height})
451 * @param {Number} [height] 高度
452 * @return {Object|Browser} 如果无宽度和高度参数,则返回当前大小对象,否则返回当前Browser实例
453 */
454 size: function(width, height){
45514 var self = this,
456 windowHandle = self.windowHandle;
45714 if(width === undefined){
458 //返回首个窗口的大小
4598 return self.doCommand('getWindowSize', {'pathValues': {'windowHandle': windowHandle}});
460 }
4616 else if(width.width !== undefined){
4622 height = width.height;
4632 width = width.width;
464 }
4656 self.doCommand('setWindowSize', {
466 'pathValues': {'windowHandle': windowHandle},
467 'width': width,
468 'height': height
469 });
4706 return self;
471 },
472
473 /**
474 * 最大化当前窗口
475 * @method maximize
476 * @public
477 * @return {Browser} 当前Browser对象实例
478 */
479 maximize: function(){
4802 var self = this;
4812 self.doCommand('maximizeWindow', {'pathValues': {'windowHandle': self.windowHandle}});
4822 return self;
483 },
484
485 /**
486 * 返回或者设置当前窗口的坐标
487 * @method offset
488 * @public
489 * @param {Number|Object} [x] x坐标或者坐标对象({x,y})
490 * @param {Number} [y] y坐标
491 * @return {Object|Browser} 如果无x坐标和y坐标参数,则返回当前坐标对象,否则返回当前Browser实例
492 */
493 offset: function(x, y){
4948 var self = this,
495 windowHandle = self.windowHandle;
4968 if(x === undefined){
497 //返回首个窗口的大小
4984 return self.doCommand('getWindowOffset', {'pathValues': {'windowHandle': windowHandle}});
499 }
5004 else if(x.x !== undefined){
5012 y = x.y;
5022 x = x.x;
503 }
5044 self.doCommand('setWindowOffset', {
505 'pathValues': {'windowHandle': windowHandle},
506 'x': x,
507 'y': y
508 });
5094 return self;
510 },
511
512 /**
513 * 关闭当前窗口
514 * @method close
515 * @public
516 * @return {Browser} 当前Browser对象实例
517 */
518 close: function(){
5196 var self = this;
5206 self.doCommand('closeWindow');
5216 return self;
522 },
523
524 /**
525 * 结束浏览器会话
526 * @method end
527 * @public
528 * @return {Browser} 当前Browser对象实例
529 */
530 end: function(){
5310 var self = this;
5320 self.doCommand('delSession');
5330 return self;
534 },
535
536 /**
537 * 设置操作超时时间
538 * <p>
539 * 注:script包括同步和异步两种代码
540 * </p>
541 * @method setTimeout
542 * @public
543 * @param {String} type 操作类型(script|ascript|implicit|page load)
544 * @param {Number} ms 超时时间
545 * @return {Browser} 当前Browser对象实例
546 */
547 setTimeout: function(type, ms){
5484 var self = this;
5494 if(type === 'ascript'){
5504 self.doCommand('setAscriptTimeout', {'ms': ms});
551 }
552 else{
5530 self.doCommand('setTimeouts', {'type': type, 'ms': ms});
554 }
5554 return self;
556 },
557
558 /**
559 * 打开或者返回当前URL
560 * @method url
561 * @public
562 * @param {String} [url] 需要打开的URL网址,若省略此参数则返回当前浏览器URL
563 * @return {String|Browser} 若打开URL则返回当前Browser对象实例,否则返回URL地址
564 */
565 url: function(url){
56624 var self = this;
56724 if(url){
56814 self.doCommand('setUrl', {'url': url});
56914 return self;
570 }
571 else {
57210 return self.doCommand('getUrl');
573 }
574 },
575
576 /**
577 * 控制浏览器回到后一个URL
578 * @method forward
579 * @public
580 * @return {Browser} 当前Browser对象实例
581 */
582 forward: function(){
5832 var self = this;
5842 self.doCommand('setForward');
5852 return self;
586 },
587
588 /**
589 * 控制浏览器回到前一个URL
590 * @method back
591 * @public
592 * @return {Browser} 当前Browser对象实例
593 */
594 back: function(){
5952 var self = this;
5962 self.doCommand('setBack');
5972 return self;
598 },
599
600 /**
601 * 刷新当前页面
602 * @method refresh
603 * @public
604 * @return {Browser} 当前Browser对象实例
605 */
606 refresh: function(){
6072 var self = this;
6082 self.doCommand('setRefresh');
6092 return self;
610 },
611
612 /**
613 * 返回当前页面title
614 * @method title
615 * @public
616 * @return {String} 页面title
617 */
618 title: function(){
6192 return this.doCommand('getTitle');
620 },
621
622 /**
623 * 返回当前页面源代码
624 * @method source
625 * @public
626 * @return {String} 页面源代码
627 */
628 source: function(){
6292 return this.doCommand('getSource');
630 },
631
632 /**
633 * 返回当前页面下所有cookie
634 * @method getCookies
635 * @public
636 * @return {Array} cookie对象数组
637 */
638 getCookies: function(){
63910 return this.doCommand('getAllCookie');
640 },
641
642 /**
643 * 添加cookie到当前页面
644 * <p>
645 * 这里可以查看WebDriver官方定义的<a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object" target="_blank">cookie对象</a>
646 * </p>
647 * @method addCookie
648 * @public
649 * @param {Object} cookie cookie对象
650 * @return {Browser} 当前Browser对象实例
651 */
652 addCookie: function(cookie){
6534 var self = this;
6544 self.doCommand('setCookie', {'cookie': cookie});
6554 return self;
656 },
657
658 /**
659 * 删除cookie
660 * @method delCookies
661 * @public
662 * @param {String} name cookie name
663 * @return {Browser} 当前Browser对象实例
664 */
665 delCookies: function(name){
6664 var self = this;
6674 if(name !== undefined){
6682 self.doCommand('delCookie', {'pathValues': {'name': name}});
669 }
670 else{
6712 self.doCommand('delAllCookies');
672 }
6734 return self;
674 },
675
676 /**
677 * 执行Javascript脚本
678 * @method exec
679 * @public
680 * @param {String} script Javascript代码
681 * @param {Array|Object} [args] 传递给脚本的参数,此参数可省略
682 * @param {Boolean} bAsync 是否异步,如果是异步必需要调用callback返回,否则一直阻塞
683 * @return {Object} Javascript脚本的返回值
684 */
685 exec: function(script, args, bAsync){
68624 if(typeof args === 'boolean'){
6874 bAsync = args;
6884 args = [];
689 }
69024 args = args?args:[];
69124 if(Object.prototype.toString.apply(args) !== '[object Array]'){
692 //单参数模式
6936 args = [args];
694 }
69524 var arg;
69624 for(var i in args){
69714 arg = args[i];
69814 if(arg instanceof Element){
699 //转换为原生Element对象
7002 args[i] = arg.toArray();
701 }
702 }
70324 var data = {'script': script, args: args};
70424 return this.doCommand(bAsync === true?'executeAsync':'execute', data);
705 },
706
707 /**
708 * 获取弹出窗口的文本(alert, confirm, prompt)
709 * @method getAlert
710 * @public
711 * @return {String} 弹出窗口的文本
712 */
713 getAlert: function(){
7142 return this.doCommand('getAlert');
715 },
716
717 /**
718 * 输入prompt中的值
719 * @method setAlert
720 * @public
721 * @return {Browser} 当前Browser对象实例
722 */
723 setAlert: function(str){
7240 var self = this;
7250 self.doCommand('setAlert', {'text': str});
7260 return self;
727 },
728
729 /**
730 * 关闭弹出窗口
731 * @method closeAlert
732 * @public
733 * @param {String} [btn] 点击哪个按钮关闭窗口:ok|cancel,默认cancel
734 * @return {Browser} 当前Browser对象实例
735 */
736 closeAlert: function(btn){
7372 var self = this;
7382 self.doCommand(/^ok$/i.test(btn)?'acceptAlert':'dismissAlert');
7392 return self;
740 },
741
742 /**
743 * 获取或保存当前网页截图
744 * <p>
745 * filePath为可选项,若有则保存到文件
746 * </p>
747 * @method getScreenshot
748 * @public
749 * @param {String} [filePath] 保存截图的文件路径
750 * @return {String} Base64格式的网页截图
751 */
752 getScreenshot: function(filePath){
7532 var data = this.doCommand('getScreenshot');
7542 if(filePath){
7552 fs.writeFileSync(filePath, data, "base64");
756 }
7572 return data;
758 },
759
760 /**
761 * 获取按键映射后的字符数组
762 * @method _getKeyArray
763 * @private
764 * @param {String} str 原始字符串
765 * @return {Array} 经过映射后的字符数组
766 */
767 _getKeyArray: function(str){
76814 str = str.replace(/{(\w+)}/g, function(all, name){
7694 var key = arrSpecKeys[name.toLowerCase()];
7704 return key?key:all;
771 });
77214 return str.split('');
773 },
774
775 /**
776 * 发送按键序列到当前焦点对象上
777 * @method sendKeys
778 * @public
779 * @param {String} str 原始字符串
780 * @return {Browser} 当前Browser对象实例
781 */
782 sendKeys: function(str){
7832 var self = this;
7842 self.doCommand('sendKeys', {'value': self._getKeyArray(str)});
7852 return self;
786 },
787
788 /**
789 * 在当前光标处触发单击事件
790 * @method click
791 * @public
792 * @param {String} mouseBtn 鼠标按钮类型:left|middle|right
793 * @return {Browser} 当前Browser对象实例
794 */
795 click: function(mouseBtn){
7964 var self = this, data = {};
7974 if(mouseBtn){
7980 var button = arrMouseButton[mouseBtn.toLowerCase()];
7990 if(button){
8000 data.button = button;
801 }
802 }
8034 self.doCommand('click', data);
8044 return self;
805 },
806
807 /**
808 * 在当前光标处触发双击事件
809 * @method dblclick
810 * @public
811 * @return {Browser} 当前Browser对象实例
812 */
813 dblclick: function(){
8142 var self = this;
8152 self.doCommand('dblclick');
8162 return self;
817 },
818
819 /**
820 * 在当前光标处触发鼠标左键按下
821 * @method mousedown
822 * @public
823 * @return {Browser} 当前Browser对象实例
824 */
825 mousedown: function(){
8262 var self = this;
8272 self.doCommand('mousedown');
8282 return self;
829 },
830
831 /**
832 * 移动到指定对象中间或坐标点
833 * @method mousemove
834 * @public
835 * @param {Number|Element|Object} x X坐标或Element对象或{x,y}
836 * @param {Number} y y坐标
837 * @return {Browser} 当前Browser对象实例
838 */
839 mousemove: function(x, y){
8406 var self = this, data = {};
8416 if(x instanceof Element){
8424 data.element = x.toArray().ELEMENT;
843 }
8442 else if(x.x && x.y){
8450 data.xoffset = x.x;
8460 data.yoffset = x.y;
847 }
848 else{
8492 data.xoffset = x;
8502 data.yoffset = y;
851 }
8526 if(data.element === undefined){
8532 data.xoffset = Math.round(data.xoffset);
8542 data.yoffset = Math.round(data.yoffset);
855 }
8566 self.doCommand('mousemove', data);
8576 return self;
858 },
859
860 /**
861 * 在当前光标处触发鼠标左键按起
862 * @method mouseup
863 * @public
864 * @return {Browser} 当前Browser对象实例
865 */
866 mouseup: function(){
8672 var self = this;
8682 self.doCommand('mouseup');
8692 return self;
870 },
871
872 /**
873 * 拖放
874 * @method dragDrop
875 * @public
876 * @param {Element|Object} from 源对象或源坐标({x,y})
877 * @param {Element|Object} to 目标对象或目标坐标({x,y})
878 * @return {Browser} 当前Browser对象实例
879 */
880 dragDrop: function(from, to){
8810 var self = this;
8820 self.mousemove(from).mousedown().mousemove(to).mouseup();
8830 return self;
884 },
885
886 /**
887 * 等待对象出现或消失
888 * @method waitFor
889 * @public
890 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
891 * @param {String} value 对象选择值
892 * @param {Boolean} [targetExist] 测试目标是对象存在或不存在
893 * @param {Number} [timeout] 等待超时时间,单位毫秒,默认30000毫秒
894 * @return {Element|Object} 等待的目标如果存在,则返回Element对象,如果不存在或者超时则返回错误对象
895 */
896 waitFor: function(using, value, targetExist, timeout){
8976 var self = this,
898 fiber = Fiber.current,
899 ret, bExist, bTimeout = false;
9006 if(typeof value !== 'string'){
901 //默认css选择器
9026 timeout = targetExist;
9036 targetExist = value;
9046 value = using;
9056 using = 'css selector';
906 }
9076 if(typeof targetExist === 'number'){
9082 timeout = targetExist;
9092 targetExist = true;
910 }
9116 if(targetExist === undefined){
9122 targetExist = true;
913 }
9146 if(timeout === undefined){
9154 timeout = 30000;
916 }
9176 var _timer1, _timer2;
9186 function waitElement(){
91910 self.doCommand('getElement', {'using': using,'value': value}, function(result){
92010 bExist = self.isOk(result);
92110 ret = result;
92210 if(bExist === targetExist){
9234 clearTimeout(_timer2);
9244 fiber.run();
925 }
9266 else if(bTimeout === true){
9272 fiber.run();
928 }
9294 else if(_timer1 !== null){
930 //每隔500毫秒确认目标是否存在
9314 _timer1 = setTimeout(waitElement, 500);
932 }
933 });
934 }
9356 _timer1 = setTimeout(waitElement, 1);
9366 _timer2 = setTimeout(function(){
9372 self.log('ERROR', 'waitFor timeout: ' + using + ' , ' + value);
938 //标记超时,getElement返回时结束当前等待请求
9392 bTimeout = true;
940 }, timeout);
9416 Fiber.yield();
9426 return bExist === true ? self.element('element', ret.ELEMENT) : ret;
943 },
944
945 /**
946 * 返回Element对象实例
947 * <p>
948 * 此方法不建议直接调用,建议使用run函数的第二个参数$符,例如:
949 * <pre>
950 * wd.run(function(browser, $){
951 * $('#id').val('test');
952 * })
953 * </pre>
954 * </p>
955 * @method element
956 * @public
957 * @param {String} [using] 对象选择类型,留空默认为:css selector(class name|css selector|id|name|link text|partial link text|tag name|xpath)
958 * @param {String} [value] 对象选择值,留空返回当前焦点所在对象
959 * @return {Element} Element的实例对象,如果对象不存在,返回原始消息
960 */
961 element: function(using, value){
96282 var self = this;
96382 var element = new Element(self, using, value);
96482 return self.isError(element._id) ? element._id : element;
965 }
966
967}
968
9691module.exports = Browser;

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;

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;