arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
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});