openEventsPlatformApp/plugins/cordova-plugin-camera/appium-tests/android/android.spec.js
plugins/cordova-plugin-camera/appium-tests/android/android.spec.jsRaw
1/*jshint node: true, jasmine: true */
2
3/*
4 *
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 *
22*/
23
24// these tests are meant to be executed by Cordova ParaMedic Appium runner
25// you can find it here: https://github.com/apache/cordova-paramedic/
26// it is not necessary to do a full CI setup to run these tests
27// Run:
28// node cordova-paramedic/main.js --platform android --plugin cordova-plugin-camera --skipMainTests --target <emulator name>
29// Please note only Android 5.1 and 4.4 are supported at this point.
30
31'use strict';
32
33var wdHelper = global.WD_HELPER;
34var screenshotHelper = global.SCREENSHOT_HELPER;
35var wd = wdHelper.getWD();
36var cameraConstants = require('../../www/CameraConstants');
37var cameraHelper = require('../helpers/cameraHelper');
38
39var MINUTE = 60 * 1000;
40var BACK_BUTTON = 4;
41var DEFAULT_SCREEN_WIDTH = 360;
42var DEFAULT_SCREEN_HEIGHT = 567;
43var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
44var PROMISE_PREFIX = 'appium_camera_promise_';
45var CONTEXT_NATIVE_APP = 'NATIVE_APP';
46
47describe('Camera tests Android.', function () {
48 var driver;
49 // the name of webview context, it will be changed to match needed context if there are named ones:
50 var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
51 // this indicates that the device library has the test picture:
52 var isTestPictureSaved = false;
53 // we need to know the screen width and height to properly click on an image in the gallery:
54 var screenWidth = DEFAULT_SCREEN_WIDTH;
55 var screenHeight = DEFAULT_SCREEN_HEIGHT;
56 // promise count to use in promise ID
57 var promiseCount = 0;
58 // determine if Appium session is created successfully
59 var appiumSessionStarted = false;
60 // determine if camera is present on the device/emulator
61 var cameraAvailable = false;
62 // determine if emulator is within a range of acceptable resolutions able to run these tests
63 var isResolutionBad = true;
64 // a path to the image we add to the gallery before test run
65 var fillerImagePath;
66
67 function getNextPromiseId() {
68 promiseCount += 1;
69 return getCurrentPromiseId();
70 }
71
72 function getCurrentPromiseId() {
73 return PROMISE_PREFIX + promiseCount;
74 }
75
76 function gracefullyFail(error) {
77 fail(error);
78 return driver
79 .quit()
80 .then(function () {
81 return getDriver();
82 });
83 }
84
85 // combinines specified options in all possible variations
86 // you can add more options to test more scenarios
87 function generateOptions() {
88 var sourceTypes = [
89 cameraConstants.PictureSourceType.CAMERA,
90 cameraConstants.PictureSourceType.PHOTOLIBRARY
91 ];
92 var destinationTypes = cameraConstants.DestinationType;
93 var encodingTypes = cameraConstants.EncodingType;
94 var allowEditOptions = [ true, false ];
95 var correctOrientationOptions = [ true, false ];
96
97 return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
98 }
99
100 // invokes Camera.getPicture() with the specified options
101 // and goes through all UI interactions unless 'skipUiInteractions' is true
102 function getPicture(options, skipUiInteractions) {
103 var promiseId = getNextPromiseId();
104 if (!options) {
105 options = {};
106 }
107
108 return driver
109 .context(webviewContext)
110 .execute(cameraHelper.getPicture, [options, promiseId])
111 .context(CONTEXT_NATIVE_APP)
112 .then(function () {
113 if (skipUiInteractions) {
114 return;
115 }
116 // selecting a picture from gallery
117 if (options.hasOwnProperty('sourceType') &&
118 (options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
119 options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM)) {
120 var tapTile = new wd.TouchAction();
121 var swipeRight = new wd.TouchAction();
122 tapTile
123 .tap({
124 x: Math.round(screenWidth / 4),
125 y: Math.round(screenHeight / 4)
126 });
127 swipeRight
128 .press({x: 10, y: Math.round(screenHeight / 4)})
129 .wait(300)
130 .moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
131 .wait(1500)
132 .release()
133 .wait(1000);
134 if (options.allowEdit) {
135 return driver
136 // always wait before performing touchAction
137 .sleep(7000)
138 .performTouchAction(tapTile);
139 }
140 return driver
141 .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
142 .fail(function () {
143 // If the Gallery button is not present, swipe right to reveal the Gallery button!
144 return driver
145 .performTouchAction(swipeRight)
146 .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
147 })
148 .click()
149 // always wait before performing touchAction
150 .sleep(7000)
151 .performTouchAction(tapTile);
152 }
153 // taking a picture from camera
154 return driver
155 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
156 .click()
157 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
158 .click();
159 })
160 .then(function () {
161 if (skipUiInteractions) {
162 return;
163 }
164 if (options.allowEdit) {
165 return driver
166 .waitForElementByAndroidUIAutomator('new UiSelector().text("Save")', MINUTE)
167 .click();
168 }
169 })
170 .fail(function (failure) {
171 throw failure;
172 });
173 }
174
175 // checks if the picture was successfully taken
176 // if shouldLoad is falsy, ensures that the error callback was called
177 function checkPicture(shouldLoad, options) {
178 if (!options) {
179 options = {};
180 }
181 return driver
182 .context(webviewContext)
183 .setAsyncScriptTimeout(MINUTE / 2)
184 .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
185 .then(function (result) {
186 if (shouldLoad) {
187 if (result !== 'OK') {
188 fail(result);
189 }
190 } else if (result.indexOf('ERROR') === -1) {
191 throw 'Unexpected success callback with result: ' + result;
192 }
193 });
194 }
195
196 // deletes the latest image from the gallery
197 function deleteImage() {
198 var holdTile = new wd.TouchAction();
199 holdTile
200 .press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)})
201 .wait(1000)
202 .release();
203 return driver
204 // always wait before performing touchAction
205 .sleep(7000)
206 .performTouchAction(holdTile)
207 .elementByAndroidUIAutomator('new UiSelector().text("Delete")')
208 .then(function (element) {
209 return element
210 .click()
211 .elementByAndroidUIAutomator('new UiSelector().text("OK")')
212 .click();
213 }, function () {
214 // couldn't find Delete menu item. Possibly there is no image.
215 return driver;
216 });
217 }
218
219 function getDriver() {
220 driver = wdHelper.getDriver('Android');
221 return driver.getWebviewContext()
222 .then(function(context) {
223 webviewContext = context;
224 return driver.context(webviewContext);
225 })
226 .waitForDeviceReady()
227 .injectLibraries()
228 .then(function () {
229 var options = {
230 quality: 50,
231 allowEdit: false,
232 sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
233 saveToPhotoAlbum: false,
234 targetWidth: 210,
235 targetHeight: 210
236 };
237 return driver
238 .then(function () { return getPicture(options, true); })
239 .context(CONTEXT_NATIVE_APP)
240 // case insensitive select, will be handy with Android 7 support
241 .elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]')
242 .click()
243 .fail(function noAlert() { })
244 .deviceKeyEvent(BACK_BUTTON)
245 .sleep(2000)
246 .elementById('action_bar_title')
247 .then(function () {
248 // success means we're still in native app
249 return driver
250 .deviceKeyEvent(BACK_BUTTON);
251 }, function () {
252 // error means we're already in webview
253 return driver;
254 });
255 })
256 .then(function () {
257 // doing it inside a function because otherwise
258 // it would not hook up to the webviewContext var change
259 // in the first methods of this chain
260 return driver.context(webviewContext);
261 })
262 .deleteFillerImage(fillerImagePath)
263 .then(function () {
264 fillerImagePath = null;
265 })
266 .addFillerImage()
267 .then(function (result) {
268 if (result && result.indexOf('ERROR:') === 0) {
269 throw new Error(result);
270 } else {
271 fillerImagePath = result;
272 }
273 });
274 }
275
276 function recreateSession() {
277 return driver
278 .quit()
279 .finally(function () {
280 return getDriver();
281 });
282 }
283
284 function tryRunSpec(spec) {
285 return driver
286 .then(spec)
287 .fail(function () {
288 return recreateSession()
289 .then(spec)
290 .fail(function() {
291 return recreateSession()
292 .then(spec);
293 });
294 })
295 .fail(gracefullyFail);
296 }
297
298 // produces a generic spec function which
299 // takes a picture with specified options
300 // and then verifies it
301 function generateSpec(options) {
302 return function () {
303 return driver
304 .then(function () {
305 return getPicture(options);
306 })
307 .then(function () {
308 return checkPicture(true, options);
309 });
310 };
311 }
312
313 function checkSession(done, skipResolutionCheck) {
314 if (!appiumSessionStarted) {
315 fail('Failed to start a session ' + (lastFailureReason ? lastFailureReason : ''));
316 done();
317 }
318 if (!skipResolutionCheck && isResolutionBad) {
319 fail('The resolution of this target device is not within the appropriate range of width: blah-blah and height: bleh-bleh. The target\'s current resolution is: ' + isResolutionBad);
320 }
321 }
322
323 function checkCamera(pending) {
324 if (!cameraAvailable) {
325 pending('This test requires a functioning camera on the Android device/emulator, and this test suite\'s functional camera test failed on your target environment.');
326 }
327 }
328
329 afterAll(function (done) {
330 checkSession(done);
331 driver
332 .quit()
333 .done(done);
334 }, MINUTE);
335
336 it('camera.ui.util configuring driver and starting a session', function (done) {
337 getDriver()
338 .then(function () {
339 appiumSessionStarted = true;
340 }, fail)
341 .done(done);
342 }, 10 * MINUTE);
343
344 it('camera.ui.util determine screen dimensions', function (done) {
345 checkSession(done, /*skipResolutionCheck?*/ true); // skip the resolution check here since we are about to find out in this spec!
346 driver
347 .context(CONTEXT_NATIVE_APP)
348 .getWindowSize()
349 .then(function (size) {
350 screenWidth = Number(size.width);
351 screenHeight = Number(size.height);
352 isResolutionBad = false;
353 /*
354 TODO: what are acceptable resolution values?
355 need to check what the emulators used in CI return.
356 and also what local device definitions work and dont
357 */
358 })
359 .done(done);
360 }, MINUTE);
361
362 it('camera.ui.util determine camera availability', function (done) {
363 checkSession(done);
364 var opts = {
365 sourceType: cameraConstants.PictureSourceType.CAMERA,
366 saveToPhotoAlbum: false
367 };
368
369 return driver
370 .then(function () {
371 return getPicture(opts);
372 })
373 .then(function () {
374 cameraAvailable = true;
375 }, function () {
376 return recreateSession();
377 })
378 .done(done);
379 }, 5 * MINUTE);
380
381 describe('Specs.', function () {
382 // getPicture() with saveToPhotoLibrary = true
383 it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
384 checkSession(done);
385 checkCamera(pending);
386 var spec = generateSpec({
387 quality: 50,
388 allowEdit: false,
389 sourceType: cameraConstants.PictureSourceType.CAMERA,
390 saveToPhotoAlbum: true
391 });
392
393 tryRunSpec(spec)
394 .then(function () {
395 isTestPictureSaved = true;
396 })
397 .done(done);
398 }, 10 * MINUTE);
399
400 // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
401 it('camera.ui.spec.2 Selecting only videos', function (done) {
402 checkSession(done);
403 var spec = function () {
404 var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
405 mediaType: cameraConstants.MediaType.VIDEO };
406 return driver
407 .then(function () {
408 return getPicture(options, true);
409 })
410 .context(CONTEXT_NATIVE_APP)
411 .then(function () {
412 // try to find "Gallery" menu item
413 // if there's none, the gallery should be already opened
414 return driver
415 .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery")', 20000)
416 .then(function (element) {
417 return element.click();
418 }, function () {
419 return driver;
420 });
421 })
422 .then(function () {
423 // if the gallery is opened on the videos page,
424 // there should be a "Choose video" caption
425 return driver
426 .elementByAndroidUIAutomator('new UiSelector().text("Choose video")')
427 .fail(function () {
428 throw 'Couldn\'t find a "Choose video" element.';
429 });
430 })
431 .deviceKeyEvent(BACK_BUTTON)
432 .elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
433 .deviceKeyEvent(BACK_BUTTON)
434 .finally(function () {
435 return driver
436 .elementById('action_bar_title')
437 .then(function () {
438 // success means we're still in native app
439 return driver
440 .deviceKeyEvent(BACK_BUTTON)
441 // give native app some time to close
442 .sleep(2000)
443 // try again! because every ~30th build
444 // on Sauce Labs this backbutton doesn't work
445 .elementById('action_bar_title')
446 .then(function () {
447 // success means we're still in native app
448 return driver
449 .deviceKeyEvent(BACK_BUTTON);
450 }, function () {
451 // error means we're already in webview
452 return driver;
453 });
454 }, function () {
455 // error means we're already in webview
456 return driver;
457 });
458 });
459 };
460 tryRunSpec(spec).done(done);
461 }, 10 * MINUTE);
462
463 // getPicture(), then dismiss
464 // wait for the error callback to be called
465 it('camera.ui.spec.3 Dismissing the camera', function (done) {
466 checkSession(done);
467 checkCamera(pending);
468 var spec = function () {
469 var options = {
470 quality: 50,
471 allowEdit: true,
472 sourceType: cameraConstants.PictureSourceType.CAMERA,
473 destinationType: cameraConstants.DestinationType.FILE_URI
474 };
475 return driver
476 .then(function () {
477 return getPicture(options, true);
478 })
479 .context(CONTEXT_NATIVE_APP)
480 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
481 .click()
482 .then(function () {
483 return checkPicture(false);
484 });
485 };
486
487 tryRunSpec(spec).done(done);
488 }, 10 * MINUTE);
489
490 // getPicture(), then take picture but dismiss the edit
491 // wait for the error callback to be called
492 it('camera.ui.spec.4 Dismissing the edit', function (done) {
493 checkSession(done);
494 checkCamera(pending);
495 var spec = function () {
496 var options = {
497 quality: 50,
498 allowEdit: true,
499 sourceType: cameraConstants.PictureSourceType.CAMERA,
500 destinationType: cameraConstants.DestinationType.FILE_URI
501 };
502 return driver
503 .then(function () {
504 return getPicture(options, true);
505 })
506 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
507 .click()
508 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
509 .click()
510 .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*discard.*")', MINUTE / 2)
511 .click()
512 .then(function () {
513 return checkPicture(false);
514 });
515 };
516
517 tryRunSpec(spec).done(done);
518 }, 10 * MINUTE);
519
520 it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
521 checkSession(done);
522 checkCamera(pending);
523 var spec = generateSpec({
524 quality: 50,
525 allowEdit: false,
526 sourceType: cameraConstants.PictureSourceType.CAMERA,
527 saveToPhotoAlbum: false,
528 targetWidth: 210,
529 targetHeight: 210
530 });
531
532 tryRunSpec(spec).done(done);
533 }, 10 * MINUTE);
534
535 it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
536 checkSession(done);
537 var spec = generateSpec({
538 quality: 50,
539 allowEdit: false,
540 sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
541 saveToPhotoAlbum: false,
542 targetWidth: 210,
543 targetHeight: 210
544 });
545
546 tryRunSpec(spec).done(done);
547 }, 10 * MINUTE);
548
549 it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
550 checkSession(done);
551 checkCamera(pending);
552 var spec = generateSpec({
553 quality: 50,
554 allowEdit: false,
555 sourceType: cameraConstants.PictureSourceType.CAMERA,
556 destinationType: cameraConstants.DestinationType.NATIVE_URI,
557 saveToPhotoAlbum: false,
558 targetWidth: 210,
559 targetHeight: 210
560 });
561
562 tryRunSpec(spec).done(done);
563 }, 10 * MINUTE);
564
565 it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
566 checkSession(done);
567 var spec = generateSpec({
568 quality: 50,
569 allowEdit: false,
570 sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
571 destinationType: cameraConstants.DestinationType.NATIVE_URI,
572 saveToPhotoAlbum: false,
573 targetWidth: 210,
574 targetHeight: 210
575 });
576
577 tryRunSpec(spec).done(done);
578 }, 10 * MINUTE);
579
580 it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
581 checkSession(done);
582 checkCamera(pending);
583 var spec = generateSpec({
584 quality: 100,
585 allowEdit: true,
586 sourceType: cameraConstants.PictureSourceType.CAMERA,
587 destinationType: cameraConstants.DestinationType.NATIVE_URI,
588 saveToPhotoAlbum: false,
589 targetWidth: 305,
590 targetHeight: 305
591 });
592
593 tryRunSpec(spec).done(done);
594 }, 10 * MINUTE);
595
596 it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
597 checkSession(done);
598 var spec = generateSpec({
599 quality: 100,
600 allowEdit: true,
601 sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
602 destinationType: cameraConstants.DestinationType.NATIVE_URI,
603 saveToPhotoAlbum: false,
604 targetWidth: 305,
605 targetHeight: 305
606 });
607
608 tryRunSpec(spec).done(done);
609 }, 10 * MINUTE);
610
611 // combine various options for getPicture()
612 generateOptions().forEach(function (spec) {
613 it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
614 checkSession(done);
615 if (spec.options.sourceType == cameraConstants.PictureSourceType.CAMERA) {
616 checkCamera(pending);
617 }
618 var s = generateSpec(spec.options);
619 tryRunSpec(s).done(done);
620 }, 10 * MINUTE);
621 });
622
623 it('camera.ui.util Delete filler picture from device library', function (done) {
624 driver
625 .context(webviewContext)
626 .deleteFillerImage(fillerImagePath)
627 .done(done);
628 }, MINUTE);
629
630 it('camera.ui.util Delete taken picture from device library', function (done) {
631 checkSession(done);
632 if (!isTestPictureSaved) {
633 // couldn't save test picture earlier, so nothing to delete here
634 done();
635 return;
636 }
637 // delete exactly one latest picture
638 // this should be the picture we've taken in the first spec
639 driver
640 .context(CONTEXT_NATIVE_APP)
641 .deviceKeyEvent(BACK_BUTTON)
642 .sleep(1000)
643 .deviceKeyEvent(BACK_BUTTON)
644 .sleep(1000)
645 .deviceKeyEvent(BACK_BUTTON)
646 .elementById('Apps')
647 .click()
648 .then(function () {
649 return driver
650 .elementByXPath('//android.widget.Button[@text="OK"]')
651 .click()
652 .fail(function () {
653 // no cling is all right
654 // it is not a brand new emulator, then
655 });
656 })
657 .elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
658 .click()
659 .elementByAndroidUIAutomator('new UiSelector().textContains("Pictures")')
660 .click()
661 .then(deleteImage)
662 .deviceKeyEvent(BACK_BUTTON)
663 .sleep(1000)
664 .deviceKeyEvent(BACK_BUTTON)
665 .sleep(1000)
666 .deviceKeyEvent(BACK_BUTTON)
667 .fail(fail)
668 .finally(done);
669 }, 3 * MINUTE);
670 });
671
672});