ios.spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. */
  21. // these tests are meant to be executed by Cordova Paramedic test runner
  22. // you can find it here: https://github.com/apache/cordova-paramedic/
  23. // it is not necessary to do a full CI setup to run these tests
  24. // just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera"
  25. 'use strict';
  26. var wdHelper = global.WD_HELPER;
  27. var screenshotHelper = global.SCREENSHOT_HELPER;
  28. var isDevice = global.DEVICE;
  29. var cameraConstants = require('../../www/CameraConstants');
  30. var cameraHelper = require('../helpers/cameraHelper');
  31. var MINUTE = 60 * 1000;
  32. var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
  33. var PROMISE_PREFIX = 'appium_camera_promise_';
  34. var CONTEXT_NATIVE_APP = 'NATIVE_APP';
  35. describe('Camera tests iOS.', function () {
  36. var driver;
  37. var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
  38. // promise count to use in promise ID
  39. var promiseCount = 0;
  40. // going to set this to false if session is created successfully
  41. var failedToStart = true;
  42. // points out which UI automation to use
  43. var isXCUI = false;
  44. // spec counter to restart the session
  45. var specsRun = 0;
  46. function getNextPromiseId() {
  47. promiseCount += 1;
  48. return getCurrentPromiseId();
  49. }
  50. function getCurrentPromiseId() {
  51. return PROMISE_PREFIX + promiseCount;
  52. }
  53. function gracefullyFail(error) {
  54. fail(error);
  55. return driver
  56. .quit()
  57. .then(function () {
  58. return getDriver();
  59. });
  60. }
  61. // generates test specs by combining all the specified options
  62. // you can add more options to test more scenarios
  63. function generateOptions() {
  64. var sourceTypes = cameraConstants.PictureSourceType;
  65. var destinationTypes = cameraConstants.DestinationType;
  66. var encodingTypes = cameraConstants.EncodingType;
  67. var allowEditOptions = [ true, false ];
  68. var correctOrientationOptions = [ true, false ];
  69. return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
  70. }
  71. function usePicture(allowEdit) {
  72. return driver
  73. .sleep(10)
  74. .then(function () {
  75. if (isXCUI) {
  76. return driver.waitForElementByAccessibilityId('Choose', MINUTE / 3).click();
  77. } else {
  78. if (allowEdit) {
  79. return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
  80. }
  81. return driver.elementByXPath('//*[@label="Use"]').click();
  82. }
  83. });
  84. }
  85. function clickPhoto() {
  86. if (isXCUI) {
  87. // iOS >=10
  88. return driver
  89. .context(CONTEXT_NATIVE_APP)
  90. .elementsByXPath('//XCUIElementTypeCell')
  91. .then(function(photos) {
  92. if (photos.length == 0) {
  93. return driver
  94. .sleep(0) // driver.source is not a function o.O
  95. .source()
  96. .then(function (src) {
  97. console.log(src);
  98. gracefullyFail('Couldn\'t find an image to click');
  99. });
  100. }
  101. // intentionally clicking the second photo here
  102. // the first one is not clickable for some reason
  103. return photos[1].click();
  104. });
  105. }
  106. // iOS <10
  107. return driver
  108. .elementByXPath('//UIACollectionCell')
  109. .click();
  110. }
  111. function getPicture(options, cancelCamera, skipUiInteractions) {
  112. var promiseId = getNextPromiseId();
  113. if (!options) {
  114. options = {};
  115. }
  116. // assign defaults
  117. if (!options.hasOwnProperty('allowEdit')) {
  118. options.allowEdit = true;
  119. }
  120. if (!options.hasOwnProperty('destinationType')) {
  121. options.destinationType = cameraConstants.DestinationType.FILE_URI;
  122. }
  123. if (!options.hasOwnProperty('sourceType')) {
  124. options.destinationType = cameraConstants.PictureSourceType.CAMERA;
  125. }
  126. return driver
  127. .context(webviewContext)
  128. .execute(cameraHelper.getPicture, [options, promiseId])
  129. .context(CONTEXT_NATIVE_APP)
  130. .then(function () {
  131. if (skipUiInteractions) {
  132. return;
  133. }
  134. if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
  135. return driver
  136. .waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
  137. .click()
  138. .then(function () {
  139. return clickPhoto();
  140. })
  141. .then(function () {
  142. if (!options.allowEdit) {
  143. return driver;
  144. }
  145. return usePicture(options.allowEdit);
  146. });
  147. }
  148. if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
  149. return clickPhoto()
  150. .then(function () {
  151. if (!options.allowEdit) {
  152. return driver;
  153. }
  154. return usePicture(options.allowEdit);
  155. });
  156. }
  157. if (cancelCamera) {
  158. return driver
  159. .waitForElementByAccessibilityId('Cancel', MINUTE / 2)
  160. .click();
  161. }
  162. return driver
  163. .waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
  164. .click()
  165. .waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
  166. .click();
  167. })
  168. .fail(fail);
  169. }
  170. // checks if the picture was successfully taken
  171. // if shouldLoad is falsy, ensures that the error callback was called
  172. function checkPicture(shouldLoad, options) {
  173. if (!options) {
  174. options = {};
  175. }
  176. return driver
  177. .context(webviewContext)
  178. .setAsyncScriptTimeout(MINUTE / 2)
  179. .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, false])
  180. .then(function (result) {
  181. if (shouldLoad) {
  182. if (result !== 'OK') {
  183. fail(result);
  184. }
  185. } else if (result.indexOf('ERROR') === -1) {
  186. throw 'Unexpected success callback with result: ' + result;
  187. }
  188. });
  189. }
  190. // takes a picture with the specified options
  191. // and then verifies it
  192. function runSpec(options, done, pending) {
  193. if (options.sourceType === cameraConstants.PictureSourceType.CAMERA && !isDevice) {
  194. pending('Camera is not available on iOS simulator');
  195. }
  196. checkSession(done);
  197. specsRun += 1;
  198. return driver
  199. .then(function () {
  200. return getPicture(options);
  201. })
  202. .then(function () {
  203. return checkPicture(true, options);
  204. })
  205. .fail(gracefullyFail);
  206. }
  207. function getDriver() {
  208. failedToStart = true;
  209. driver = wdHelper.getDriver('iOS');
  210. return wdHelper.getWebviewContext(driver)
  211. .then(function(context) {
  212. webviewContext = context;
  213. return driver.context(webviewContext);
  214. })
  215. .then(function () {
  216. return wdHelper.waitForDeviceReady(driver);
  217. })
  218. .then(function () {
  219. return wdHelper.injectLibraries(driver);
  220. })
  221. .sessionCapabilities()
  222. .then(function (caps) {
  223. var platformVersion = parseFloat(caps.platformVersion);
  224. isXCUI = platformVersion >= 10.0;
  225. })
  226. .then(function () {
  227. var options = {
  228. quality: 50,
  229. allowEdit: false,
  230. sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
  231. saveToPhotoAlbum: false,
  232. targetWidth: 210,
  233. targetHeight: 210
  234. };
  235. return driver
  236. .then(function () { return getPicture(options, false, true); })
  237. .context(CONTEXT_NATIVE_APP)
  238. .acceptAlert()
  239. .then(function alertDismissed() {
  240. // TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+)
  241. // UI tests, we will have to:
  242. // a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest
  243. // b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert
  244. // failure callback, since we will be guaranteed to hit the permission dialog on startup.
  245. }, function noAlert() {
  246. // in case the contacts permission alert never showed up: no problem, don't freak out.
  247. // This can happen if:
  248. // a) The application-under-test already had photos permissions granted to it
  249. // b) Appium's autoAcceptAlerts capability is provided (and functioning)
  250. })
  251. .elementByAccessibilityId('Cancel', 10000)
  252. .click();
  253. })
  254. .then(function () {
  255. failedToStart = false;
  256. });
  257. }
  258. function checkSession(done) {
  259. if (failedToStart) {
  260. fail('Failed to start a session');
  261. done();
  262. }
  263. }
  264. it('camera.ui.util configure driver and start a session', function (done) {
  265. // retry up to 3 times
  266. getDriver()
  267. .fail(function () {
  268. return getDriver()
  269. .fail(function () {
  270. return getDriver()
  271. .fail(fail);
  272. });
  273. })
  274. .fail(fail)
  275. .done(done);
  276. }, 30 * MINUTE);
  277. describe('Specs.', function () {
  278. afterEach(function (done) {
  279. if (specsRun >= 19) {
  280. specsRun = 0;
  281. // we need to restart the session regularly because for some reason
  282. // when running against iOS 10 simulator on SauceLabs,
  283. // Appium cannot handle more than ~20 specs at one session
  284. // the error would be as follows:
  285. // "Could not proxy command to remote server. Original error: Error: connect ECONNREFUSED 127.0.0.1:8100"
  286. checkSession(done);
  287. return driver
  288. .quit()
  289. .then(function () {
  290. return getDriver()
  291. .fail(function () {
  292. return getDriver()
  293. .fail(function () {
  294. return getDriver()
  295. .fail(fail);
  296. });
  297. });
  298. })
  299. .done(done);
  300. } else {
  301. done();
  302. }
  303. }, 30 * MINUTE);
  304. // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
  305. it('camera.ui.spec.1 Selecting only videos', function (done) {
  306. checkSession(done);
  307. specsRun += 1;
  308. var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
  309. mediaType: cameraConstants.MediaType.VIDEO };
  310. driver
  311. // skip ui unteractions
  312. .then(function () { return getPicture(options, false, true); })
  313. .waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2)
  314. .elementByAccessibilityId('Cancel')
  315. .click()
  316. .fail(gracefullyFail)
  317. .done(done);
  318. }, 7 * MINUTE);
  319. // getPicture(), then dismiss
  320. // wait for the error callback to be called
  321. it('camera.ui.spec.2 Dismissing the camera', function (done) {
  322. checkSession(done);
  323. if (!isDevice) {
  324. pending('Camera is not available on iOS simulator');
  325. }
  326. specsRun += 1;
  327. var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
  328. saveToPhotoAlbum: false };
  329. driver
  330. .then(function () {
  331. return getPicture(options, true);
  332. })
  333. .then(function () {
  334. return checkPicture(false);
  335. })
  336. .fail(gracefullyFail)
  337. .done(done);
  338. }, 7 * MINUTE);
  339. it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
  340. var options = {
  341. quality: 50,
  342. allowEdit: false,
  343. sourceType: cameraConstants.PictureSourceType.CAMERA,
  344. saveToPhotoAlbum: false,
  345. targetWidth: 210,
  346. targetHeight: 210
  347. };
  348. runSpec(options, done, pending).done(done);
  349. }, 7 * MINUTE);
  350. it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
  351. var options = {
  352. quality: 50,
  353. allowEdit: false,
  354. sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
  355. saveToPhotoAlbum: false,
  356. targetWidth: 210,
  357. targetHeight: 210
  358. };
  359. runSpec(options, done, pending).done(done);
  360. }, 7 * MINUTE);
  361. it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
  362. var options = {
  363. quality: 50,
  364. allowEdit: false,
  365. sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
  366. saveToPhotoAlbum: false,
  367. targetWidth: 210,
  368. targetHeight: 210
  369. };
  370. runSpec(options, done, pending).done(done);
  371. }, 7 * MINUTE);
  372. it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL', function (done) {
  373. // remove this line if you don't mind the tests leaving a photo saved on device
  374. pending('Cannot prevent iOS from saving the picture to photo library');
  375. var options = {
  376. quality: 50,
  377. allowEdit: false,
  378. sourceType: cameraConstants.PictureSourceType.CAMERA,
  379. destinationType: cameraConstants.DestinationType.FILE_URL,
  380. saveToPhotoAlbum: false,
  381. targetWidth: 210,
  382. targetHeight: 210
  383. };
  384. runSpec(options, done, pending).done(done);
  385. }, 7 * MINUTE);
  386. it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
  387. var options = {
  388. quality: 50,
  389. allowEdit: false,
  390. sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
  391. destinationType: cameraConstants.DestinationType.FILE_URL,
  392. saveToPhotoAlbum: false,
  393. targetWidth: 210,
  394. targetHeight: 210
  395. };
  396. runSpec(options, done, pending).done(done);
  397. }, 7 * MINUTE);
  398. it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
  399. var options = {
  400. quality: 50,
  401. allowEdit: false,
  402. sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
  403. destinationType: cameraConstants.DestinationType.FILE_URL,
  404. saveToPhotoAlbum: false,
  405. targetWidth: 210,
  406. targetHeight: 210
  407. };
  408. runSpec(options, done, pending).done(done);
  409. }, 7 * MINUTE);
  410. it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL, quality=100', function (done) {
  411. // remove this line if you don't mind the tests leaving a photo saved on device
  412. pending('Cannot prevent iOS from saving the picture to photo library');
  413. var options = {
  414. quality: 100,
  415. allowEdit: false,
  416. sourceType: cameraConstants.PictureSourceType.CAMERA,
  417. destinationType: cameraConstants.DestinationType.FILE_URL,
  418. saveToPhotoAlbum: false,
  419. targetWidth: 305,
  420. targetHeight: 305
  421. };
  422. runSpec(options, done, pending).done(done);
  423. }, 7 * MINUTE);
  424. it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
  425. var options = {
  426. quality: 100,
  427. allowEdit: false,
  428. sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
  429. destinationType: cameraConstants.DestinationType.FILE_URL,
  430. saveToPhotoAlbum: false,
  431. targetWidth: 305,
  432. targetHeight: 305
  433. };
  434. runSpec(options, done, pending).done(done);
  435. }, 7 * MINUTE);
  436. it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
  437. var options = {
  438. quality: 100,
  439. allowEdit: false,
  440. sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
  441. destinationType: cameraConstants.DestinationType.FILE_URL,
  442. saveToPhotoAlbum: false,
  443. targetWidth: 305,
  444. targetHeight: 305
  445. };
  446. runSpec(options, done, pending).done(done);
  447. }, 7 * MINUTE);
  448. // combine various options for getPicture()
  449. generateOptions().forEach(function (spec) {
  450. it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
  451. // remove this check if you don't mind the tests leaving a photo saved on device
  452. if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
  453. spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
  454. pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
  455. 'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
  456. }
  457. runSpec(spec.options, done, pending).done(done);
  458. }, 7 * MINUTE);
  459. });
  460. });
  461. it('camera.ui.util Destroy the session', function (done) {
  462. checkSession(done);
  463. driver
  464. .quit()
  465. .done(done);
  466. }, 5 * MINUTE);
  467. });