You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

489 lines
19 KiB

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