protocol.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314
  1. var elementByRecursion = require('./element-commands/_elementByRecursion.js');
  2. var elementsByRecursion = require('./element-commands/_elementsByRecursion.js');
  3. module.exports = function(Nightwatch) {
  4. var WEBDRIVER_ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf';
  5. var MOUSE_BUTTON_LEFT = 'left',
  6. MOUSE_BUTTON_MIDDLE = 'middle',
  7. MOUSE_BUTTON_RIGHT = 'right',
  8. DIRECTION_UP = 'up',
  9. DIRECTION_DOWN = 'down';
  10. var Actions = {};
  11. //////////////////////////////////////////////////////////////////
  12. // Session related
  13. //////////////////////////////////////////////////////////////////
  14. /**
  15. * Get info about, delete or create a new session. Defaults to the current session.
  16. *
  17. * ```
  18. * this.demoTest = function (browser) {
  19. * browser.session(function(result) {
  20. * console.log(result.value);
  21. * });
  22. *
  23. * browser.session('delete', function(result) {
  24. * console.log(result.value);
  25. * });
  26. *
  27. * browser.session('delete', '12345-abc', function(result) {
  28. * console.log(result.value);
  29. * });
  30. * };
  31. * ```
  32. *
  33. * @link /session
  34. * @param {string} [action] The http verb to use, can be "get", "post" or "delete". If only the callback is passed, get is assumed as default.
  35. * @param {string} [sessionId] The id of the session to get info about or delete.
  36. * @param {function} [callback] Optional callback function to be called when the command finishes.
  37. * @api protocol
  38. */
  39. Actions.session = function(action, sessionId, callback) {
  40. var options = {
  41. path : '/session'
  42. };
  43. if (typeof arguments[0] === 'function') {
  44. callback = arguments[0];
  45. sessionId = Nightwatch.sessionId;
  46. action = 'get';
  47. } else {
  48. action = action.toLowerCase();
  49. if (typeof arguments[1] === 'function') {
  50. callback = arguments[1];
  51. sessionId = Nightwatch.sessionId;
  52. }
  53. }
  54. switch (action) {
  55. case 'delete':
  56. options.method = 'DELETE';
  57. break;
  58. case 'post':
  59. options.method = 'POST';
  60. break;
  61. case 'get':
  62. options.method = 'GET';
  63. break;
  64. default:
  65. sessionId = arguments[0];
  66. action = 'get';
  67. options.method = 'GET';
  68. }
  69. if (action != 'post') {
  70. options.path += '/' + sessionId;
  71. }
  72. return sendRequest(options, callback);
  73. };
  74. /**
  75. * Returns a list of the currently active sessions.
  76. *
  77. * @link /sessions
  78. * @param {function} [callback] Optional callback function to be called when the command finishes.
  79. * @api protocol
  80. */
  81. Actions.sessions = function(callback) {
  82. var options = {
  83. path : '/sessions',
  84. method : 'GET'
  85. };
  86. return sendRequest(options, callback);
  87. };
  88. /**
  89. * Configure the amount of time that a particular type of operation can execute for before they are aborted and a |Timeout| error is returned to the client.
  90. *
  91. * @link /session/:sessionId/timeouts
  92. * @param {string} type The type of operation to set the timeout for. Valid values are: "script" for script timeouts, "implicit" for modifying the implicit wait timeout and "page load" for setting a page load timeout.
  93. * @param {number} ms The amount of time, in milliseconds, that time-limited commands are permitted to run.
  94. * @param {function} [callback] Optional callback function to be called when the command finishes.
  95. * @api protocol
  96. */
  97. Actions.timeouts = function(type, ms, callback) {
  98. var timeoutValues = ['script', 'implicit', 'page load'];
  99. if (timeoutValues.indexOf(type) === -1) {
  100. throw new Error('Invalid timeouts type value: ' + type + '. Possible values are: ' + timeoutValues.join(','));
  101. }
  102. if (typeof ms != 'number') {
  103. throw new Error('Second argument must be number.');
  104. }
  105. return postRequest('/timeouts', {
  106. type : type,
  107. ms : ms
  108. }, callback);
  109. };
  110. /**
  111. * Set the amount of time, in milliseconds, that asynchronous scripts executed by /session/:sessionId/execute_async are permitted to run before they are aborted and a |Timeout| error is returned to the client.
  112. *
  113. * @link /session/:sessionId/timeouts/async_script
  114. * @param {number} ms The amount of time, in milliseconds, that time-limited commands are permitted to run.
  115. * @param {function} [callback] Optional callback function to be called when the command finishes.
  116. * @api protocol
  117. */
  118. Actions.timeoutsAsyncScript = function(ms, callback) {
  119. if (typeof ms != 'number') {
  120. throw new Error('First argument must be number.');
  121. }
  122. return postRequest('/timeouts/async_script', {
  123. ms : ms
  124. }, callback);
  125. };
  126. /**
  127. * Set the amount of time the driver should wait when searching for elements. If this command is never sent, the driver will default to an implicit wait of 0ms.
  128. *
  129. * @link /session/:sessionId/timeouts/implicit_wait
  130. * @param {number} ms The amount of time, in milliseconds, that time-limited commands are permitted to run.
  131. * @param {function} [callback] Optional callback function to be called when the command finishes.
  132. * @api protocol
  133. */
  134. Actions.timeoutsImplicitWait = function(ms, callback) {
  135. if (typeof ms != 'number') {
  136. throw new Error('First argument must be number.');
  137. }
  138. return postRequest('/timeouts/implicit_wait', {
  139. ms : ms
  140. }, callback);
  141. };
  142. //////////////////////////////////////////////////////////////////
  143. // Element related
  144. //////////////////////////////////////////////////////////////////
  145. /**
  146. * Search for an element on the page, starting from the document root. The located element will be returned as a WebElement JSON object.
  147. *
  148. * @link /session/:sessionId/element
  149. * @param {string} using The locator strategy to use.
  150. * @param {string} value The search target.
  151. * @param {function} [callback] Optional callback function to be called when the command finishes.
  152. * @api protocol
  153. */
  154. Actions.element = function(using, value, callback) {
  155. if (using == 'recursion') {
  156. return new elementByRecursion(Nightwatch).command(value, callback);
  157. }
  158. return element(using, value, callback);
  159. };
  160. /*!
  161. * element protocol action
  162. *
  163. * @param {string} using
  164. * @param {string} value
  165. * @param {function} callback
  166. * @private
  167. */
  168. function element(using, value, callback) {
  169. var strategies = ['class name', 'css selector', 'id', 'name', 'link text',
  170. 'partial link text', 'tag name', 'xpath'];
  171. using = using.toLocaleLowerCase();
  172. if (strategies.indexOf(using) === -1) {
  173. throw new Error('Provided locating strategy is not supported: ' +
  174. using + '. It must be one of the following:\n' +
  175. strategies.join(', '));
  176. }
  177. return postRequest('/element', {
  178. using: using,
  179. value: value
  180. }, callback);
  181. }
  182. /**
  183. * Search for an element on the page, starting from the identified element. The located element will be returned as a WebElement JSON object.
  184. *
  185. * @link /session/:sessionId/element/:id/element
  186. * @param {string} id ID of the element to route the command to.
  187. * @param {string} using The locator strategy to use.
  188. * @param {string} value The search target.
  189. * @param {function} [callback] Optional callback function to be called when the command finishes.
  190. * @api protocol
  191. */
  192. Actions.elementIdElement = function(id, using, value, callback) {
  193. var strategies = ['class name', 'css selector', 'id', 'name', 'link text',
  194. 'partial link text', 'tag name', 'xpath'];
  195. using = using.toLocaleLowerCase();
  196. if (strategies.indexOf(using) === -1) {
  197. throw new Error('Provided locating strategy is not supported: ' +
  198. using + '. It must be one of the following:\n' +
  199. strategies.join(', '));
  200. }
  201. return postRequest('/element/' + id + '/element', {
  202. using: using,
  203. value: value
  204. }, callback);
  205. };
  206. /**
  207. * Search for multiple elements on the page, starting from the document root. The located elements will be returned as a WebElement JSON objects.
  208. * Valid strings to use as locator strategies are: "class name", "css selector", "id", "name", "link text", "partial link text", "tag name", "xpath"
  209. *
  210. * @link /session/:sessionId/elements
  211. * @param {string} using The locator strategy to use.
  212. * @param {string} value The search target.
  213. * @param {function} callback Callback function to be invoked with the result when the command finishes.
  214. * @api protocol
  215. */
  216. Actions.elements = function(using, value, callback) {
  217. if (using == 'recursion') {
  218. return new elementsByRecursion(Nightwatch).command(value, callback);
  219. }
  220. return elements(using, value, callback);
  221. };
  222. /*!
  223. * elements protocol action
  224. *
  225. * @param {string} using
  226. * @param {string} value
  227. * @param {function} callback
  228. * @private
  229. */
  230. function elements(using, value, callback) {
  231. var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi;
  232. if (!check.test(using)) {
  233. throw new Error('Please provide any of the following using strings as the first parameter: ' +
  234. 'class name, css selector, id, name, link text, partial link text, tag name, or xpath. Given: ' + using);
  235. }
  236. return postRequest('/elements', {
  237. using: using,
  238. value: value
  239. }, callback);
  240. }
  241. /**
  242. * Search for multiple elements on the page, starting from the identified element. The located element will be returned as a WebElement JSON objects.
  243. *
  244. * @link /session/:sessionId/element/:id/elements
  245. * @param {string} id ID of the element to route the command to.
  246. * @param {string} using The locator strategy to use.
  247. * @param {string} value The search target.
  248. * @param {function} [callback] Optional callback function to be called when the command finishes.
  249. * @api protocol
  250. */
  251. Actions.elementIdElements = function(id, using, value, callback) {
  252. var strategies = ['class name', 'css selector', 'id', 'name', 'link text',
  253. 'partial link text', 'tag name', 'xpath'];
  254. using = using.toLocaleLowerCase();
  255. if (strategies.indexOf(using) === -1) {
  256. throw new Error('Provided locating strategy is not supported: ' +
  257. using + '. It must be one of the following:\n' +
  258. strategies.join(', '));
  259. }
  260. return postRequest('/element/' + id + '/elements', {
  261. using: using,
  262. value: value
  263. }, callback);
  264. };
  265. /**
  266. * Get the element on the page that currently has focus.
  267. *
  268. * @link /session/:sessionId/element/active
  269. * @param {function} [callback] Optional callback function to be called when the command finishes.
  270. * @api protocol
  271. */
  272. Actions.elementActive = function(callback) {
  273. return postRequest('/element/active', {}, callback);
  274. };
  275. /**
  276. * Get the value of an element's attribute.
  277. *
  278. * @link /session/:sessionId/element/:id/attribute/:name
  279. * @param {string} id ID of the element to route the command to.
  280. * @param {string} attributeName The attribute name
  281. * @param {function} [callback] Optional callback function to be called when the command finishes.
  282. * @api protocol
  283. */
  284. Actions.elementIdAttribute = function(id, attributeName, callback) {
  285. return getRequest('/element/' + id + '/attribute/' + attributeName, callback);
  286. };
  287. /**
  288. * Click on an element.
  289. *
  290. * @link /session/:sessionId/element/:id/click
  291. * @param {string} id ID of the element to route the command to.
  292. * @param {function} [callback] Optional callback function to be called when the command finishes.
  293. * @api protocol
  294. */
  295. Actions.elementIdClick = function(id, callback) {
  296. return postRequest('/element/' + id + '/click', '', callback);
  297. };
  298. /**
  299. * Query the value of an element's computed CSS property.
  300. *
  301. *
  302. * The CSS property to query should be specified using the CSS property name, not the JavaScript property name (e.g. background-color instead of backgroundColor).
  303. *
  304. * @link /session/:sessionId/element/:id/css/:propertyName
  305. * @param {string} id ID of the element to route the command to.
  306. * @param {string} cssPropertyName
  307. * @param {function} [callback] Optional callback function to be called when the command finishes.
  308. * @api protocol
  309. */
  310. Actions.elementIdCssProperty = function(id, cssPropertyName, callback) {
  311. return getRequest('/element/' + id + '/css/' + cssPropertyName, callback);
  312. };
  313. /**
  314. * Determine if an element is currently displayed.
  315. *
  316. * @link /session/:sessionId/element/:id/displayed
  317. * @param {string} id ID of the element to route the command to.
  318. * @param {function} [callback] Optional callback function to be called when the command finishes.
  319. * @api protocol
  320. */
  321. Actions.elementIdDisplayed = function(id, callback) {
  322. return getRequest('/element/' + id + '/displayed', callback);
  323. };
  324. /**
  325. * Determine an element's location on the screen once it has been scrolled into view.
  326. *
  327. * @link /session/:sessionId/element/:id/location_in_view
  328. * @param {string} id ID of the element to route the command to.
  329. * @param {function} [callback] Optional callback function to be called when the command finishes.
  330. * @api protocol
  331. */
  332. Actions.elementIdLocationInView = function(id, callback) {
  333. return getRequest('/element/' + id + '/location_in_view', callback);
  334. };
  335. /**
  336. * Determine an element's location on the page. The point (0, 0) refers to the upper-left corner of the page.
  337. *
  338. * The element's coordinates are returned as a JSON object with x and y properties.
  339. *
  340. * @link /session/:sessionId/element/:id/location
  341. * @param {string} id ID of the element to route the command to.
  342. * @param {function} [callback] Optional callback function to be called when the command finishes.
  343. * @api protocol
  344. * @returns {x:number, y:number} The X and Y coordinates for the element on the page.
  345. */
  346. Actions.elementIdLocation = function(id, callback) {
  347. return getRequest('/element/' + id + '/location', callback);
  348. };
  349. /**
  350. * Query for an element's tag name.
  351. *
  352. * @link /session/:sessionId/element/:id/name
  353. * @param {string} id ID of the element to route the command to.
  354. * @param {function} [callback] Optional callback function to be called when the command finishes.
  355. * @api protocol
  356. */
  357. Actions.elementIdName = function(id, callback) {
  358. return getRequest('/element/' + id + '/name', callback);
  359. };
  360. /**
  361. * Clear a TEXTAREA or text INPUT element's value.
  362. *
  363. * @link /session/:sessionId/element/:id/clear
  364. * @param {string} id ID of the element to route the command to.
  365. * @param {function} [callback] Optional callback function to be called when the command finishes.
  366. * @api protocol
  367. */
  368. Actions.elementIdClear = function(id, callback) {
  369. return postRequest('/element/' + id + '/clear', callback);
  370. };
  371. /**
  372. * Determine if an OPTION element, or an INPUT element of type checkbox or radio button is currently selected.
  373. *
  374. * @link /session/:sessionId/element/:id/selected
  375. * @param {string} id ID of the element to route the command to.
  376. * @param {function} [callback] Optional callback function to be called when the command finishes.
  377. * @api protocol
  378. */
  379. Actions.elementIdSelected = function(id, callback) {
  380. return getRequest('/element/' + id + '/selected', callback);
  381. };
  382. /**
  383. * Determine if an element is currently enabled.
  384. *
  385. * @link /session/:sessionId/element/:id/enabled
  386. * @param {string} id ID of the element to route the command to.
  387. * @param {function} [callback] Optional callback function to be called when the command finishes.
  388. * @api protocol
  389. */
  390. Actions.elementIdEnabled = function(id, callback) {
  391. return getRequest('/element/' + id + '/enabled', callback);
  392. };
  393. /**
  394. * Test if two element IDs refer to the same DOM element.
  395. *
  396. * @link /session/:sessionId/element/:id/equals/:other
  397. * @param {string} id ID of the element to route the command to.
  398. * @param {string} otherId ID of the element to compare against.
  399. * @param {function} [callback] Optional callback function to be called when the command finishes.
  400. * @api protocol
  401. */
  402. Actions.elementIdEquals = function(id, otherId, callback) {
  403. return getRequest('/element/' + id + '/equals/' + otherId, callback);
  404. };
  405. /**
  406. * Determine an element's size in pixels. The size will be returned as a JSON object with width and height properties.
  407. *
  408. * @link /session/:sessionId/element/:id/size
  409. * @param {string} id ID of the element to route the command to.
  410. * @param {function} [callback] Optional callback function to be called when the command finishes.
  411. * @api protocol
  412. */
  413. Actions.elementIdSize = function(id, callback) {
  414. return getRequest('/element/' + id + '/size', callback);
  415. };
  416. /**
  417. * Returns the visible text for the element.
  418. *
  419. * @link /session/:sessionId/element/:id/text
  420. * @param {string} id ID of the element to route the command to.
  421. * @param {function} [callback] Optional callback function to be called when the command finishes.
  422. * @api protocol
  423. */
  424. Actions.elementIdText = function(id, callback) {
  425. return getRequest('/element/' + id + '/text', callback);
  426. };
  427. /**
  428. * Send a sequence of key strokes to an element or returns the current value of the element.
  429. *
  430. * @link /session/:sessionId/element/:id/value
  431. * @param {string} id ID of the element to route the command to.
  432. * @param {string|array|none} [value] Value to send to element in case of a POST
  433. * @param {function} [callback] Optional callback function to be called when the command finishes.
  434. * @api protocol
  435. */
  436. Actions.elementIdValue = function(id, value, callback) {
  437. if (arguments.length === 2 && typeof arguments[1] === 'function') {
  438. callback = arguments[1];
  439. return getRequest('/element/' + id + '/attribute/value', callback);
  440. }
  441. if (Array.isArray(value)) {
  442. value = value.join('');
  443. } else {
  444. value = String(value);
  445. }
  446. return postRequest('/element/' + id + '/value', {
  447. value: value.split('')
  448. }, callback);
  449. };
  450. /**
  451. * Submit a FORM element. The submit command may also be applied to any element that is a descendant of a FORM element.
  452. *
  453. * @link /session/:sessionId/element/:id/submit
  454. * @param {string} id ID of the element to route the command to.
  455. * @param {function} [callback] Optional callback function to be called when the command finishes.
  456. * @api protocol
  457. */
  458. Actions.submit = function(id, callback) {
  459. return postRequest('/element/' + id + '/submit', '', callback);
  460. };
  461. /**
  462. * Get the current page source.
  463. *
  464. * @link /session/:sessionId/source
  465. * @param {function} [callback] Optional callback function to be called when the command finishes.
  466. * @api protocol
  467. */
  468. Actions.source = function(callback) {
  469. return getRequest('/source', callback);
  470. };
  471. //////////////////////////////////////////////////////////////////
  472. // Context related
  473. //////////////////////////////////////////////////////////////////
  474. /**
  475. * Get a list of the available contexts.
  476. *
  477. * Used by Appium when testing hybrid mobile web apps. More info here: https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/hybrid.md.
  478. *
  479. * @param {function} callback Callback function to be called when the command finishes.
  480. * @returns {Array} an array of strings representing available contexts, e.g 'WEBVIEW', or 'NATIVE'
  481. * @api protocol
  482. */
  483. Actions.contexts = function(callback) {
  484. return getRequest('/contexts', callback);
  485. };
  486. /**
  487. *
  488. * Get current context.
  489. *
  490. * @param {function} callback Callback function to be called when the command finishes.
  491. * @returns {string|null} a string representing the current context or `null`, representing "no context"
  492. * @api protocol
  493. */
  494. Actions.currentContext = function(callback) {
  495. return getRequest('/context', callback);
  496. };
  497. /**
  498. * Sets the context.
  499. *
  500. * @param {string} context context name to switch to - a string representing an available context.
  501. * @param {function} [callback] Optional callback function to be called when the command finishes.
  502. * @api protocol
  503. */
  504. Actions.setContext = function (context, callback) {
  505. var data = {
  506. name: context
  507. };
  508. return postRequest('/context', data, callback);
  509. };
  510. //////////////////////////////////////////////////////////////////
  511. // Orientation
  512. //////////////////////////////////////////////////////////////////
  513. /**
  514. * Get the current browser orientation.
  515. *
  516. * @param {function} callback Callback function to be called when the command finishes.
  517. * @returns {string} The current browser orientation: {LANDSCAPE|PORTRAIT}
  518. * @api protocol
  519. */
  520. Actions.getOrientation = function(callback) {
  521. return getRequest('/orientation', callback);
  522. };
  523. /**
  524. * Sets the browser orientation.
  525. *
  526. * @param {string} orientation The new browser orientation: {LANDSCAPE|PORTRAIT}
  527. * @param {function} [callback] Optional callback function to be called when the command finishes.
  528. * @api protocol
  529. */
  530. Actions.setOrientation = function (orientation, callback) {
  531. orientation = orientation.toUpperCase();
  532. var accepted = ['LANDSCAPE', 'PORTRAIT'];
  533. if (accepted.indexOf(orientation) == -1) {
  534. throw new Error('Invalid orientation value specified. Accepted values are: ' + accepted.join(', '));
  535. }
  536. return postRequest('/orientation', {
  537. orientation : orientation
  538. }, callback);
  539. };
  540. //////////////////////////////////////////////////////////////////
  541. // Mouse related
  542. //////////////////////////////////////////////////////////////////
  543. /**
  544. * Move the mouse by an offset of the specificed element. If no element is specified, the move is relative to the current mouse cursor. If an element is provided but no offset, the mouse will be moved to the center of the element.
  545. *
  546. * If the element is not visible, it will be scrolled into view.
  547. *
  548. * @link /session/:sessionId/moveto
  549. * @param {string} element Opaque ID assigned to the element to move to. If not specified or is null, the offset is relative to current position of the mouse.
  550. * @param {number} xoffset X offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
  551. * @param {number} yoffset Y offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
  552. * @param {function} [callback] Optional callback function to be called when the command finishes.
  553. * @api protocol
  554. */
  555. Actions.moveTo = function(element, xoffset, yoffset, callback) {
  556. var data = {};
  557. if (typeof element == 'string') {
  558. data.element = element;
  559. }
  560. if (typeof xoffset == 'number') {
  561. data.xoffset = xoffset;
  562. }
  563. if (typeof yoffset == 'number') {
  564. data.yoffset = yoffset;
  565. }
  566. return postRequest('/moveto', data, callback);
  567. };
  568. /**
  569. * Double-clicks at the current mouse coordinates (set by moveto).
  570. *
  571. * @param {function} [callback] Optional callback function to be called when the command finishes.
  572. * @api protocol
  573. */
  574. Actions.doubleClick = function(callback) {
  575. return postRequest('/doubleclick', callback);
  576. };
  577. /**
  578. * Click at the current mouse coordinates (set by moveto).
  579. *
  580. * The button can be (0, 1, 2) or ('left', 'middle', 'right'). It defaults to left mouse button, and if you don't pass in a button but do pass in a callback, it will handle it correctly.
  581. *
  582. * @link /session/:sessionId/click
  583. * @param {string|number} button The mouse button
  584. * @param {function} [callback] Optional callback function to be called when the command finishes.
  585. * @api protocol
  586. */
  587. Actions.mouseButtonClick = function(button, callback) {
  588. var buttonIndex;
  589. if (arguments.length === 0) {
  590. button = 0;
  591. } else {
  592. if (typeof(button) === 'function') {
  593. callback = button;
  594. button = 0;
  595. }
  596. if (typeof button === 'string') {
  597. buttonIndex = [
  598. MOUSE_BUTTON_LEFT,
  599. MOUSE_BUTTON_MIDDLE,
  600. MOUSE_BUTTON_RIGHT
  601. ].indexOf(button.toLowerCase());
  602. if (buttonIndex !== -1) {
  603. button = buttonIndex;
  604. }
  605. }
  606. }
  607. return postRequest('/click', {button: button}, callback);
  608. };
  609. /**
  610. * Click and hold the left mouse button (at the coordinates set by the last moveto command). Note that the next mouse-related command that should follow is `mouseButtonUp` . Any other mouse command (such as click or another call to buttondown) will yield undefined behaviour.
  611. *
  612. * Can be used for implementing drag-and-drop. The button can be (0, 1, 2) or ('left', 'middle', 'right'). It defaults to left mouse button, and if you don't pass in a button but do pass in a callback, it will handle it correctly.
  613. *
  614. * @link /session/:sessionId/buttondown
  615. * @param {string|number} button The mouse button
  616. * @param {function} [callback] Optional callback function to be called when the command finishes.
  617. * @api protocol
  618. */
  619. Actions.mouseButtonDown = function(button, callback) {
  620. return mouseButtonHandler(DIRECTION_DOWN, button, callback);
  621. };
  622. /**
  623. * Releases the mouse button previously held (where the mouse is currently at). Must be called once for every `mouseButtonDown` command issued.
  624. *
  625. * Can be used for implementing drag-and-drop. The button can be (0, 1, 2) or ('left', 'middle', 'right'). It defaults to left mouse button, and if you don't pass in a button but do pass in a callback, it will handle it correctly.
  626. *
  627. * @link /session/:sessionId/buttonup
  628. * @param {string|number} button The mouse button
  629. * @param {function} [callback] Optional callback function to be called when the command finishes.
  630. * @api protocol
  631. */
  632. Actions.mouseButtonUp = function(button, callback) {
  633. return mouseButtonHandler(DIRECTION_UP, button, callback);
  634. };
  635. /*!
  636. * Helper function for mouseButton actions
  637. *
  638. * @param {string} direction
  639. * @param {string|number} button
  640. * @param {function} callback
  641. * @private
  642. */
  643. function mouseButtonHandler(direction, button, callback) {
  644. var buttonIndex;
  645. if (typeof(button) === 'function') {
  646. callback = button;
  647. button = 0;
  648. }
  649. if (typeof button === 'string') {
  650. buttonIndex = [
  651. MOUSE_BUTTON_LEFT,
  652. MOUSE_BUTTON_MIDDLE,
  653. MOUSE_BUTTON_RIGHT
  654. ].indexOf(button.toLowerCase());
  655. if (buttonIndex !== -1) {
  656. button = buttonIndex;
  657. }
  658. }
  659. return postRequest('/button' + direction, {button: button}, callback);
  660. }
  661. /////////////////////////////////////////////////////////////////////////////
  662. // Window specific commands
  663. /////////////////////////////////////////////////////////////////////////////
  664. /*!
  665. * Helper function for execute and execute_async
  666. *
  667. * @param {string} path
  668. * @param {string|function} script
  669. * @param {Array} args
  670. * @param {function} callback
  671. * @private
  672. */
  673. function executeHandler(path, script, args, callback) {
  674. var fn;
  675. if (typeof script === 'function') {
  676. fn = 'var passedArgs = Array.prototype.slice.call(arguments,0); return ' +
  677. script.toString() + '.apply(window, passedArgs);';
  678. } else {
  679. fn = script;
  680. }
  681. if (arguments.length === 2) {
  682. args = [];
  683. } else if ((arguments.length === 3) && (typeof arguments[2] === 'function')) {
  684. callback = arguments[2];
  685. args = [];
  686. }
  687. return postRequest(path, {
  688. script: fn,
  689. args: args
  690. }, callback);
  691. }
  692. /**
  693. * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. The executed script is assumed to be synchronous and the result of evaluating the script is returned to the client.
  694. * The script argument defines the script to execute in the form of a function body. The value returned by that function will be returned to the client.
  695. *
  696. * The function will be invoked with the provided args array and the values may be accessed via the arguments object in the order specified.
  697. *
  698. * ```
  699. * this.demoTest = function (browser) {
  700. * browser.execute(function(data) {
  701. * // resize operation
  702. * return true;
  703. * }, [imagedata], function(result) {
  704. * ...
  705. * });
  706. * };
  707. * ```
  708. *
  709. * @link /session/:sessionId/execute
  710. * @param {string|function} body The function body to be injected.
  711. * @param {Array} args An array of arguments which will be passed to the function.
  712. * @param {function} [callback] Optional callback function to be called when the command finishes.
  713. * @api protocol
  714. * @returns {*} The script result.
  715. */
  716. Actions.execute = function(body, args, callback) {
  717. var executeArgs = Array.prototype.slice.call(arguments, 0);
  718. executeArgs.unshift('/execute');
  719. return executeHandler.apply(null, executeArgs);
  720. };
  721. /**
  722. * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. The executed script is assumed to be asynchronous and the result of evaluating the script is returned to the client.
  723. *
  724. * Asynchronous script commands may not span page loads. If an unload event is fired while waiting for a script result, an error should be returned to the client.
  725. *
  726. * ```
  727. * this.demoTest = function (browser) {
  728. * browser.executeAsync(function(data, done) {
  729. * someAsyncOperation(function() {
  730. * done(true);
  731. * });
  732. * }, [imagedata], function(result) {
  733. * // ...
  734. * });
  735. * };
  736. * ```
  737. *
  738. * @link /session/:sessionId/execute_async
  739. * @param {string|function} script The function body to be injected.
  740. * @param {Array} args An array of arguments which will be passed to the function.
  741. * @param {function} [callback] Optional callback function to be called when the command finishes.
  742. * @api protocol
  743. * @returns {*} The script result.
  744. */
  745. Actions.executeAsync = function(script, args, callback) {
  746. args = Array.prototype.slice.call(arguments, 0);
  747. args.unshift('/execute_async');
  748. return executeHandler.apply(null, args);
  749. };
  750. /*!
  751. * @deprecated
  752. */
  753. Actions.execute_async = Actions.executeAsync;
  754. /**
  755. * Change focus to another frame on the page. If the frame id is missing or null, the server should switch to the page's default content.
  756. *
  757. * @link /session/:sessionId/frame
  758. * @param {string|number|null} [frameId] Identifier for the frame to change focus to.
  759. * @param {function} [callback] Optional callback function to be called when the command finishes.
  760. * @api protocol
  761. */
  762. Actions.frame = function(frameId, callback) {
  763. if (arguments.length === 1 && typeof frameId === 'function') {
  764. callback = frameId;
  765. return postRequest('/frame', callback);
  766. }
  767. return postRequest('/frame', {
  768. id: frameId
  769. }, callback);
  770. };
  771. /**
  772. * Change focus to the parent context. If the current context is the top level browsing context, the context remains unchanged.
  773. *
  774. * @link /session/:sessionId/frame/parent
  775. * @param {function} [callback] Optional callback function to be called when the command finishes.
  776. * @since v0.4.8
  777. * @api protocol
  778. */
  779. Actions.frameParent = function(callback) {
  780. return postRequest('/frame/parent', callback);
  781. };
  782. /**
  783. * Change focus to another window or close the current window.
  784. *
  785. * @link /session/:sessionId/window
  786. * @param {string} method The HTTP method to use
  787. * @param {string} handleOrName The window to change focus to.
  788. * @param {function} [callback] Optional callback function to be called when the command finishes.
  789. * @since v0.3.0
  790. * @api protocol
  791. */
  792. Actions.window = function(method, handleOrName, callback) {
  793. method = method.toUpperCase();
  794. switch (method) {
  795. case 'POST':
  796. if (arguments.length < 2) {
  797. throw new Error('POST requests to /window must include a name parameter also.');
  798. }
  799. return postRequest('/window', {
  800. name : handleOrName
  801. }, callback);
  802. case 'DELETE':
  803. return deleteRequest('/window', arguments[1]);
  804. default:
  805. throw new Error('This method expects first argument to be either POST or DELETE.');
  806. }
  807. };
  808. /**
  809. * Retrieve the current window handle.
  810. *
  811. * @link /session/:sessionId/window_handle
  812. * @param {function} [callback] Optional callback function to be called when the command finishes.
  813. * @api protocol
  814. */
  815. Actions.windowHandle = function(callback) {
  816. return getRequest('/window_handle', callback);
  817. };
  818. /**
  819. * Retrieve the current window handle.
  820. *
  821. * @link /session/:sessionId/window/:windowHandle/maximize
  822. * @param {string} [handleOrName] windowHandle URL parameter; if it is "current", the currently active window will be maximized.
  823. * @param {function} [callback] Optional callback function to be called when the command finishes.
  824. * @api protocol
  825. */
  826. Actions.windowMaximize = function(handleOrName, callback) {
  827. return postRequest('/window/'+ handleOrName + '/maximize', callback);
  828. };
  829. /*!
  830. * @deprecated
  831. */
  832. Actions.window_handle = Actions.windowHandle;
  833. /**
  834. * Retrieve the list of all window handles available to the session.
  835. *
  836. * @link /session/:sessionId/window_handles
  837. * @param {function} [callback] Optional callback function to be called when the command finishes.
  838. * @api protocol
  839. */
  840. Actions.windowHandles = function(callback) {
  841. return getRequest('/window_handles', callback);
  842. };
  843. /*!
  844. * @deprecated
  845. */
  846. Actions.window_handles = Actions.windowHandles;
  847. /**
  848. * Change or get the size of the specified window. If the second argument is a function it will be used as a callback and the call will perform a get request to retrieve the existing window size.
  849. *
  850. * @link /session/:sessionId/window/:windowHandle/size
  851. * @param {string} windowHandle
  852. * @param {number} width
  853. * @param {number} height
  854. * @param {function} [callback] Optional callback function to be called when the command finishes.
  855. * @api protocol
  856. */
  857. Actions.windowSize = function(windowHandle, width, height, callback) {
  858. if (typeof windowHandle !== 'string') {
  859. throw new Error('First argument must be a window handle string.');
  860. }
  861. var path = '/window/' + windowHandle + '/size';
  862. if (arguments.length === 2 && typeof arguments[1] === 'function') {
  863. return getRequest(path, arguments[1]);
  864. }
  865. width = Number(width);
  866. height = Number(height);
  867. if (typeof width !== 'number' || isNaN(width)) {
  868. throw new Error('Width and height arguments must be passed as numbers.');
  869. }
  870. if (typeof height !== 'number' || isNaN(height)) {
  871. throw new Error('Width and height arguments must be passed as numbers.');
  872. }
  873. return postRequest(path, {
  874. width : width,
  875. height : height
  876. }, callback);
  877. };
  878. /**
  879. * Change or get the position of the specified window. If the second argument is a function it will be used as a callback and the call will perform a get request to retrieve the existing window position.
  880. *
  881. * @link /session/:sessionId/window/:windowHandle/position
  882. * @param {string} windowHandle
  883. * @param {number} offsetX
  884. * @param {number} offsetY
  885. * @param {function} [callback] Optional callback function to be called when the command finishes.
  886. * @api protocol
  887. */
  888. Actions.windowPosition = function(windowHandle, offsetX, offsetY, callback) {
  889. if (typeof windowHandle !== 'string') {
  890. throw new Error('First argument must be a window handle string.');
  891. }
  892. var path = '/window/' + windowHandle + '/position';
  893. if (arguments.length === 2 && typeof arguments[1] === 'function') {
  894. return getRequest(path, arguments[1]);
  895. }
  896. offsetX = Number(offsetX);
  897. offsetY = Number(offsetY);
  898. if (typeof offsetX !== 'number' || isNaN(offsetX)) {
  899. throw new Error('Offset arguments must be passed as numbers.');
  900. }
  901. if (typeof offsetY !== 'number' || isNaN(offsetY)) {
  902. throw new Error('Offset arguments must be passed as numbers.');
  903. }
  904. return postRequest(path, {
  905. x : offsetX,
  906. y : offsetY
  907. }, callback);
  908. };
  909. /**
  910. * Refresh the current page.
  911. *
  912. * @link /session/:sessionId/refresh
  913. * @param {function} [callback] Optional callback function to be called when the command finishes.
  914. * @api protocol
  915. */
  916. Actions.refresh = function(callback) {
  917. return postRequest('/refresh', callback);
  918. };
  919. /**
  920. * Navigate backwards in the browser history, if possible.
  921. *
  922. * @link /session/:sessionId/back
  923. * @param {function} [callback] Optional callback function to be called when the command finishes.
  924. * @api protocol
  925. */
  926. Actions.back = function(callback) {
  927. return postRequest('/back', callback);
  928. };
  929. /**
  930. * Navigate forwards in the browser history, if possible.
  931. *
  932. * @link /session/:sessionId/back
  933. * @param {function} [callback] Optional callback function to be called when the command finishes.
  934. * @api protocol
  935. */
  936. Actions.forward = function(callback) {
  937. return postRequest('/forward', callback);
  938. };
  939. /**
  940. * Take a screenshot of the current page.
  941. *
  942. * @link /session/:sessionId/screenshot
  943. * @param {boolean} log_screenshot_data Whether or not the screenshot data should appear in the logs when running with --verbose
  944. * @param {function} [callback] Optional callback function to be called when the command finishes.
  945. * @api protocol
  946. */
  947. Actions.screenshot = function(log_screenshot_data, callback) {
  948. return getRequest('/screenshot', callback).on('beforeResult', function(result) {
  949. result = result || {};
  950. if (!log_screenshot_data) {
  951. result.suppressBase64Data = true;
  952. }
  953. });
  954. };
  955. /**
  956. * Retrieve the URL of the current page or navigate to a new URL.
  957. *
  958. * @link /session/:sessionId/url
  959. * @param {string|function} [url] If missing, it will return the URL of the current page as an argument to the supplied callback
  960. * @param {Function} [callback]
  961. * @api protocol
  962. */
  963. Actions.url = function(url, callback) {
  964. if (typeof url == 'string') {
  965. return postRequest('/url', {
  966. url : url
  967. }, callback);
  968. }
  969. if (typeof url == 'function') {
  970. callback = url;
  971. }
  972. return getRequest('/url', callback);
  973. };
  974. /**
  975. * Query the server's current status.
  976. *
  977. * @link /status
  978. * @param {function} [callback] Optional callback function to be called when the command finishes.
  979. * @api protocol
  980. */
  981. Actions.status = function(callback) {
  982. return sendRequest({
  983. method : 'GET',
  984. path : '/status'
  985. }, callback);
  986. };
  987. /**
  988. * Get the current page title.
  989. *
  990. * @link /session/:sessionId/title
  991. * @param {function} [callback] Optional callback function to be called when the command finishes.
  992. * @api protocol
  993. */
  994. Actions.title = function(callback) {
  995. return getRequest('/title', callback);
  996. };
  997. /**
  998. * Send a sequence of key strokes to the active element. The sequence is defined in the same format as the `sendKeys` command.
  999. * An object map with available keys and their respective UTF-8 characters, as defined on [W3C WebDriver draft spec](http://www.w3.org/TR/webdriver/#character-types), is loaded onto the main Nightwatch instance as `client.Keys`.
  1000. *
  1001. * Rather than the `setValue`, the modifiers are not released at the end of the call. The state of the modifier keys is kept between calls, so mouse interactions can be performed while modifier keys are depressed.
  1002. *
  1003. * @link /session/:sessionId/keys
  1004. * @param {Array} keysToSend The keys sequence to be sent.
  1005. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1006. * @api protocol
  1007. */
  1008. Actions.keys = function(keysToSend, callback) {
  1009. if (!Array.isArray(keysToSend)) {
  1010. keysToSend = [keysToSend];
  1011. }
  1012. return postRequest('/keys', {
  1013. value: keysToSend
  1014. }, callback);
  1015. };
  1016. /////////////////////////////////////////////////////////////////////////////
  1017. // Cookies
  1018. /////////////////////////////////////////////////////////////////////////////
  1019. /**
  1020. * Retrieve or delete all cookies visible to the current page or set a cookie.
  1021. *
  1022. * @link /session/:sessionId/cookie
  1023. * @param {string} method Http method
  1024. * @param {function|object} [callbackOrCookie] Optional callback function to be called when the command finishes.
  1025. * @since v0.4.0
  1026. * @api protocol
  1027. */
  1028. Actions.cookie = function(method, callbackOrCookie) {
  1029. switch (method) {
  1030. case 'GET':
  1031. return getRequest('/cookie', callbackOrCookie);
  1032. case 'POST':
  1033. if (arguments.length < 2) {
  1034. throw new Error('POST requests to /cookie must include a cookie object parameter also.');
  1035. }
  1036. return postRequest('/cookie', {
  1037. cookie : callbackOrCookie
  1038. }, arguments[2]);
  1039. case 'DELETE':
  1040. if (typeof callbackOrCookie === 'undefined' || typeof callbackOrCookie === 'function') {
  1041. return deleteRequest('/cookie', callbackOrCookie);
  1042. }
  1043. return deleteRequest('/cookie/' + callbackOrCookie, arguments[2]);
  1044. default:
  1045. throw new Error('This method expects first argument to be either GET, POST or DELETE.');
  1046. }
  1047. };
  1048. /////////////////////////////////////////////////////////////////////////////
  1049. // Alert handling
  1050. /////////////////////////////////////////////////////////////////////////////
  1051. /**
  1052. * Accepts the currently displayed alert dialog. Usually, this is equivalent to clicking on the 'OK' button in the dialog.
  1053. *
  1054. * @link /session/:sessionId/accept_alert
  1055. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1056. * @api protocol
  1057. */
  1058. Actions.acceptAlert = function(callback) {
  1059. return postRequest('/accept_alert', callback);
  1060. };
  1061. /*!
  1062. * @deprecated
  1063. */
  1064. Actions.accept_alert = Actions.acceptAlert;
  1065. /**
  1066. * Dismisses the currently displayed alert dialog. For confirm() and prompt() dialogs, this is equivalent to clicking the 'Cancel' button.
  1067. *
  1068. * For alert() dialogs, this is equivalent to clicking the 'OK' button.
  1069. *
  1070. * @link /session/:sessionId/dismiss_alert
  1071. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1072. * @api protocol
  1073. */
  1074. Actions.dismissAlert = function(callback) {
  1075. return postRequest('/dismiss_alert', callback);
  1076. };
  1077. /**
  1078. * Sends keystrokes to a JavaScript prompt() dialog.
  1079. *
  1080. * @link /session/:sessionId/alert_text
  1081. * @param {string} value Keystrokes to send to the prompt() dialog
  1082. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1083. * @api protocol
  1084. */
  1085. Actions.setAlertText = function(value, callback) {
  1086. return postRequest('/alert_text', {text: value}, callback);
  1087. };
  1088. /**
  1089. * Gets the text of the currently displayed JavaScript alert(), confirm(), or prompt() dialog.
  1090. *
  1091. * @link /session/:sessionId/alert_text
  1092. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1093. * @returns {string} The text of the currently displayed alert.
  1094. * @api protocol
  1095. */
  1096. Actions.getAlertText = function(callback) {
  1097. return getRequest('/alert_text', callback);
  1098. };
  1099. /*!
  1100. * @deprecated
  1101. */
  1102. Actions.dismiss_alert = Actions.dismissAlert;
  1103. /**
  1104. * Gets the text of the log type specified
  1105. *
  1106. * @link /session/:sessionId/log
  1107. * @param {string} typeString Type of log to request
  1108. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1109. * @returns {Array} Array of the text entries of the log.
  1110. * @api protocol
  1111. */
  1112. Actions.sessionLog = function(typeString, callback) {
  1113. return postRequest('/log', {type: typeString}, callback);
  1114. };
  1115. /**
  1116. * Gets an array of strings for which log types are available.
  1117. *
  1118. * @link /session/:sessionId/log/types
  1119. * @param {function} [callback] Optional callback function to be called when the command finishes.
  1120. * @returns {Array} Available log types
  1121. * @api protocol
  1122. */
  1123. Actions.sessionLogTypes = function(callback) {
  1124. return getRequest('/log/types', callback);
  1125. };
  1126. /////////////////////////////////////////////////////////////////////////////
  1127. // Helpers
  1128. /////////////////////////////////////////////////////////////////////////////
  1129. function getRequest(path, callback) {
  1130. var options = {
  1131. path : '/session/' + Nightwatch.sessionId + path,
  1132. method : 'GET'
  1133. };
  1134. return sendRequest(options, callback);
  1135. }
  1136. function deleteRequest(path, callback) {
  1137. var options = {
  1138. path : '/session/' + Nightwatch.sessionId + path,
  1139. method : 'DELETE'
  1140. };
  1141. return sendRequest(options, callback);
  1142. }
  1143. function validateElementEntry(result) {
  1144. if (!result.ELEMENT && result[WEBDRIVER_ELEMENT_ID]) {
  1145. result.ELEMENT = result[WEBDRIVER_ELEMENT_ID];
  1146. delete result[WEBDRIVER_ELEMENT_ID];
  1147. }
  1148. return result;
  1149. }
  1150. function postRequest(path, data, callback) {
  1151. if (arguments.length === 2 && typeof data === 'function') {
  1152. callback = data;
  1153. data = '';
  1154. }
  1155. var options = {
  1156. path : '/session/' + Nightwatch.sessionId + path,
  1157. method : 'POST',
  1158. data : data || ''
  1159. };
  1160. return sendRequest(options, function(result) {
  1161. if (/\/element$/.test(options.path) && result.value) {
  1162. result.value = validateElementEntry(result.value);
  1163. } else if (/\/elements$/.test(options.path) && Array.isArray(result.value)) {
  1164. result.value = result.value.map(function(entry) {
  1165. return validateElementEntry(entry);
  1166. });
  1167. }
  1168. if (typeof callback === 'function') {
  1169. callback.call(this, result);
  1170. }
  1171. });
  1172. }
  1173. function sendRequest(options, callback) {
  1174. callback = callback || function() {};
  1175. return Nightwatch.runProtocolAction(options, callback).send();
  1176. }
  1177. return Actions;
  1178. };