From 220c7af204ce98ded9b6999c28d6c245104725f6 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 21 Jan 2024 20:44:35 +0100 Subject: [PATCH] Update lock specs --- lib/commands/lock.js | 28 +- test/unit/commands/lock-specs.js | 462 ++++++++++++++++++++++++------- test/unit/unlock-helper-specs.js | 351 ----------------------- 3 files changed, 379 insertions(+), 462 deletions(-) delete mode 100644 test/unit/unlock-helper-specs.js diff --git a/lib/commands/lock.js b/lib/commands/lock.js index 57391e79..7dfa2a38 100644 --- a/lib/commands/lock.js +++ b/lib/commands/lock.js @@ -16,9 +16,9 @@ const UNLOCK_TYPES = /** @type {const} */ ([ PATTERN_UNLOCK, FINGERPRINT_UNLOCK, ]); -const KEYCODE_NUMPAD_ENTER = 66; -const UNLOCK_WAIT_TIME = 100; -const INPUT_KEYS_WAIT_TIME = 100; +export const KEYCODE_NUMPAD_ENTER = 66; +export const UNLOCK_WAIT_TIME = 100; +export const INPUT_KEYS_WAIT_TIME = 100; const NUMBER_ZERO_KEYCODE = 7; /** @@ -208,7 +208,7 @@ async function fastUnlock(adb, opts) { * @param {string} key * @returns {string} */ -function encodePassword(key) { +export function encodePassword(key) { return `${key}`.replace(/\s/gi, '%s'); } @@ -217,7 +217,7 @@ function encodePassword(key) { * @param {string} key * @returns {string[]} */ -function stringKeyToArr(key) { +export function stringKeyToArr(key) { return `${key}`.trim().replace(/\s+/g, '').split(/\s*/); } @@ -226,7 +226,7 @@ function stringKeyToArr(key) { * @param {AndroidDriverCaps} capabilities * @returns {Promise} */ -async function fingerprintUnlock(capabilities) { +export async function fingerprintUnlock(capabilities) { if ((await this.adb.getApiLevel()) < 23) { throw new Error('Fingerprint unlock only works for Android 6+ emulators'); } @@ -239,7 +239,7 @@ async function fingerprintUnlock(capabilities) { * @param {AndroidDriverCaps} capabilities * @returns {Promise} */ -async function pinUnlock(capabilities) { +export async function pinUnlock(capabilities) { logger.info(`Trying to unlock device using pin ${capabilities.unlockKey}`); await this.adb.dismissKeyguard(); const keys = stringKeyToArr(String(capabilities.unlockKey)); @@ -276,7 +276,7 @@ async function pinUnlock(capabilities) { * @param {AndroidDriverCaps} capabilities * @returns {Promise} */ -async function pinUnlockWithKeyEvent(capabilities) { +export async function pinUnlockWithKeyEvent(capabilities) { logger.info(`Trying to unlock device using pin with keycode ${capabilities.unlockKey}`); await this.adb.dismissKeyguard(); const keys = stringKeyToArr(String(capabilities.unlockKey)); @@ -296,7 +296,7 @@ async function pinUnlockWithKeyEvent(capabilities) { * @param {AndroidDriverCaps} capabilities * @returns {Promise} */ -async function passwordUnlock(capabilities) { +export async function passwordUnlock(capabilities) { const {unlockKey} = capabilities; logger.info(`Trying to unlock device using password ${unlockKey}`); await this.adb.dismissKeyguard(); @@ -318,7 +318,7 @@ async function passwordUnlock(capabilities) { * @param {number} piece * @returns {import('@appium/types').Position} */ -function getPatternKeyPosition(key, initPos, piece) { +export function getPatternKeyPosition(key, initPos, piece) { /* How the math works: We have 9 buttons divided in 3 columns and 3 rows inside the lockPatternView, @@ -342,7 +342,7 @@ function getPatternKeyPosition(key, initPos, piece) { * @param {number} piece * @returns {import('./types').TouchAction[]} */ -function getPatternActions(keys, initPos, piece) { +export function getPatternActions(keys, initPos, piece) { /** @type {import('./types').TouchAction[]} */ const actions = []; /** @type {number[]} */ @@ -398,7 +398,7 @@ function getPatternActions(keys, initPos, piece) { * @param {import('appium-adb').ADB} adb * @param {number?} [timeoutMs=null] */ -async function verifyUnlock(adb, timeoutMs = null) { +export async function verifyUnlock(adb, timeoutMs = null) { try { await waitForCondition(async () => !(await adb.isScreenLocked()), { waitMs: timeoutMs ?? 2000, @@ -414,7 +414,7 @@ async function verifyUnlock(adb, timeoutMs = null) { * @this {import('../driver').AndroidDriver} * @param {AndroidDriverCaps} capabilities */ -async function patternUnlock(capabilities) { +export async function patternUnlock(capabilities) { const {unlockKey} = capabilities; logger.info(`Trying to unlock device using pattern ${unlockKey}`); await this.adb.dismissKeyguard(); @@ -449,7 +449,7 @@ async function patternUnlock(capabilities) { * @param {AndroidDriverCaps?} [caps=null] * @returns {Promise} */ -async function unlockWithOptions(caps = null) { +export async function unlockWithOptions(caps = null) { if (!(await this.adb.isScreenLocked())) { logger.info('Screen already unlocked, doing nothing'); return; diff --git a/test/unit/commands/lock-specs.js b/test/unit/commands/lock-specs.js index bd7e808f..772cbc2f 100644 --- a/test/unit/commands/lock-specs.js +++ b/test/unit/commands/lock-specs.js @@ -2,12 +2,29 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import sinon from 'sinon'; import ADB from 'appium-adb'; -import {withMocks} from '@appium/test-support'; import { AndroidDriver } from '../../../lib/driver'; +import { + unlockWithOptions, + validateUnlockCapabilities, + encodePassword, + stringKeyToArr, + UNLOCK_WAIT_TIME, + fingerprintUnlock, + passwordUnlock, + pinUnlock, + KEYCODE_NUMPAD_ENTER, + INPUT_KEYS_WAIT_TIME, + getPatternKeyPosition, + getPatternActions, + patternUnlock, +} from '../../../lib/commands/lock'; +import * as unlockHelpers from '../../../lib/commands/lock'; +import * as asyncboxHelpers from 'asyncbox'; chai.use(chaiAsPromised); describe('Lock', function () { + /** @type {AndroidDriver} */ let driver; let sandbox = sinon.createSandbox(); @@ -17,104 +34,355 @@ describe('Lock', function () { driver.adb = adb; }); afterEach(function () { - sandbox.restore(); + sandbox.verifyAndRestore(); }); - describe( - 'unlock', - withMocks({adb, helpers, unlocker}, (mocks) => { - it('should return if screen is already unlocked', async function () { - mocks.adb.expects('isScreenLocked').withExactArgs().once().returns(false); - mocks.adb.expects('getApiLevel').never(); - mocks.adb.expects('startApp').never(); - mocks.adb.expects('isLockManagementSupported').never(); - await helpers.unlock(helpers, adb, {}); - mocks.adb.verify(); - }); - it('should start unlock app', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('dismissKeyguard').once(); - mocks.adb.expects('isLockManagementSupported').never(); - await helpers.unlock(helpers, adb, {}); - mocks.adb.verify(); - mocks.helpers.verify(); - }); - it('should raise an error on undefined unlockKey when unlockType is defined', async function () { - mocks.adb.expects('isScreenLocked').once().returns(true); - mocks.adb.expects('isLockManagementSupported').never(); - await helpers.unlock(helpers, adb, {unlockType: 'pin'}).should.be.rejected; - mocks.adb.verify(); - mocks.unlocker.verify(); - mocks.helpers.verify(); - }); - it('should call pinUnlock if unlockType is pin', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(false); - mocks.unlocker.expects('pinUnlock').once(); - await helpers.unlock(helpers, adb, {unlockType: 'pin', unlockKey: '1111'}); - mocks.adb.verify(); - mocks.helpers.verify(); - mocks.unlocker.verify(); - }); - it('should call pinUnlock if unlockType is pinWithKeyEvent', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(false); - mocks.unlocker.expects('pinUnlockWithKeyEvent').once(); - await helpers.unlock(helpers, adb, {unlockType: 'pinWithKeyEvent', unlockKey: '1111'}); - mocks.adb.verify(); - mocks.helpers.verify(); - mocks.unlocker.verify(); - }); - it('should call fastUnlock if unlockKey is provided', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(true); - mocks.helpers.expects('verifyUnlock').once(); - mocks.unlocker.expects('fastUnlock').once(); - await helpers.unlock(helpers, adb, {unlockKey: 'appium', unlockType: 'password'}); - mocks.adb.verify(); - mocks.unlocker.verify(); - mocks.helpers.verify(); - }); - it('should call passwordUnlock if unlockType is password', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(false); - mocks.unlocker.expects('passwordUnlock').once(); - await helpers.unlock(helpers, adb, {unlockType: 'password', unlockKey: 'appium'}); - mocks.adb.verify(); - mocks.helpers.verify(); - mocks.unlocker.verify(); - }); - it('should call patternUnlock if unlockType is pattern', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(false); - mocks.unlocker.expects('patternUnlock').once(); - await helpers.unlock(helpers, adb, {unlockType: 'pattern', unlockKey: '123456789'}); - mocks.adb.verify(); - mocks.helpers.verify(); - }); - it('should call fingerprintUnlock if unlockType is fingerprint', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').never(); - mocks.unlocker.expects('fingerprintUnlock').once(); - await helpers.unlock(helpers, adb, {unlockType: 'fingerprint', unlockKey: '1111'}); - mocks.adb.verify(); - mocks.unlocker.verify(); + describe('unlockWithOptions', function () { + it('should return if screen is already unlocked', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').withExactArgs().once().returns(false); + sandbox.stub(driver.adb, 'getApiLevel').never(); + sandbox.stub(driver.adb, 'startApp').never(); + sandbox.stub(driver.adb, 'isLockManagementSupported').never(); + await unlockWithOptions.bind(driver)({}); + }); + it('should start unlock app', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(driver.adb, 'isLockManagementSupported').never(); + await unlockWithOptions.bind(driver)({}); + }); + it('should raise an error on undefined unlockKey when unlockType is defined', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').once().returns(true); + sandbox.stub(driver.adb, 'isLockManagementSupported').never(); + await unlockWithOptions.bind(driver)({unlockType: 'pin'}).should.be.rejected; + }); + it('should call pinUnlock if unlockType is pin', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(false); + sandbox.stub(unlockHelpers, 'pinUnlock').once(); + await unlockWithOptions.bind(driver)({unlockType: 'pin', unlockKey: '1111'}); + }); + it('should call pinUnlock if unlockType is pinWithKeyEvent', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(false); + sandbox.stub(unlockHelpers, 'pinUnlockWithKeyEvent').once(); + await unlockWithOptions.bind(driver)({unlockType: 'pinWithKeyEvent', unlockKey: '1111'}); + }); + it('should call fastUnlock if unlockKey is provided', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(true); + sandbox.stub(unlockHelpers, 'verifyUnlock').once(); + sandbox.stub(unlockHelpers, 'fastUnlock').once(); + await unlockWithOptions.bind(driver)({unlockKey: 'appium', unlockType: 'password'}); + }); + it('should call passwordUnlock if unlockType is password', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(false); + sandbox.stub(unlockHelpers, 'passwordUnlock').once(); + await unlockWithOptions.bind(driver)({unlockType: 'password', unlockKey: 'appium'}); + }); + it('should call patternUnlock if unlockType is pattern', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(false); + sandbox.stub(unlockHelpers, 'patternUnlock').once(); + await unlockWithOptions.bind(driver)({unlockType: 'pattern', unlockKey: '123456789'}); + }); + it('should call fingerprintUnlock if unlockType is fingerprint', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').never(); + sandbox.stub(unlockHelpers, 'fingerprintUnlock').once(); + await unlockWithOptions.bind(driver)({unlockType: 'fingerprint', unlockKey: '1111'}); + }); + it('should throw an error is api is lower than 23 and trying to use fingerprintUnlock', async function () { + sandbox.stub(driver.adb, 'isScreenLocked').onCall(0).returns(true); + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(driver.adb, 'isLockManagementSupported').onCall(0).returns(false); + sandbox.stub(driver.adb, 'getApiLevel').once().returns(21); + await unlockWithOptions.bind(driver)({unlockType: 'fingerprint', unlockKey: '1111'}) + .should.be.rejectedWith('Fingerprint'); + }); + }); + describe('validateUnlockCapabilities', function () { + function toCaps(unlockType, unlockKey) { + return { + unlockType, + unlockKey, + }; + } + + it('should verify the unlock keys for pin/pinWithKeyEvent', function () { + for (const invalidValue of [undefined, ' ', '1abc']) { + chai.expect(() => validateUnlockCapabilities(toCaps('pin', invalidValue))).to.throw; + chai.expect(() => validateUnlockCapabilities(toCaps('pinWithKeyEvent', invalidValue))).to + .throw; + } + validateUnlockCapabilities(toCaps('pin', '1111')); + validateUnlockCapabilities(toCaps('pinWithKeyEvent', '1111')); + }); + it('should verify the unlock keys for fingerprint', function () { + for (const invalidValue of [undefined, ' ', '1abc']) { + chai.expect(() => validateUnlockCapabilities(toCaps('fingerprint', invalidValue))).to + .throw; + } + validateUnlockCapabilities(toCaps('fingerprint', '1')); + }); + it('should verify the unlock keys for pattern', function () { + for (const invalidValue of [undefined, '1abc', '', '1', '1213', '01234', ' ']) { + chai.expect(() => validateUnlockCapabilities(toCaps('pattern', invalidValue))).to.throw; + } + for (const validValue of ['1234', '123456789']) { + validateUnlockCapabilities(toCaps('pattern', validValue)); + } + }); + it('should verify the unlock keys for password', function () { + for (const invalidValue of [undefined, '123', ' ']) { + chai.expect(() => validateUnlockCapabilities(toCaps('password', invalidValue))).to.throw; + } + for (const validValue of [ + '121c3', + 'appium', + 'appium-android-driver', + '@#$%&-+()*"\':;!?,_ ./~`|={}\\[]', + ]) { + validateUnlockCapabilities(toCaps('password', validValue)); + } + }); + it('should throw error if unlock type is invalid', function () { + chai.expect(() => validateUnlockCapabilities(toCaps('invalid_unlock_type', '1'))).to.throw; + }); + }); + describe('encodePassword', function () { + it('should verify the password with blank space is encoded', function () { + encodePassword('a p p i u m').should.equal('a%sp%sp%si%su%sm'); + encodePassword(' ').should.equal('%s%s%s'); + }); + }); + describe('stringKeyToArr', function () { + it('should cast string keys to array', function () { + stringKeyToArr('1234').should.eql(['1', '2', '3', '4']); + stringKeyToArr(' 1234 ').should.eql(['1', '2', '3', '4']); + stringKeyToArr('1 2 3 4').should.eql(['1', '2', '3', '4']); + stringKeyToArr('1 2 3 4').should.eql(['1', '2', '3', '4']); + }); + }); + describe('fingerprintUnlock', function () { + it('should be able to unlock device via fingerprint if API level >= 23', async function () { + let caps = {unlockKey: '123'}; + sandbox.stub(driver.adb, 'getApiLevel').returns(23); + sandbox.stub(driver.adb, 'fingerprint').withExactArgs(caps.unlockKey).once(); + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); + await fingerprintUnlock.bind(driver)(caps).should.be.fulfilled; + }); + it('should throw error if API level < 23', async function () { + sandbox.stub(driver.adb, 'getApiLevel').returns(22); + sandbox.stub(driver.adb, 'fingerprint').never(); + sandbox.stub(asyncboxHelpers, 'sleep').never(); + await fingerprintUnlock.bind(driver)({}) + .should.eventually.be.rejectedWith('only works for Android 6+'); + }); + }); + describe('pinUnlock', function() { + const caps = {unlockKey: '13579'}; + const keys = ['1', '3', '5', '7', '9']; + const els = [ + {ELEMENT: 1}, + {ELEMENT: 2}, + {ELEMENT: 3}, + {ELEMENT: 4}, + {ELEMENT: 5}, + {ELEMENT: 6}, + {ELEMENT: 7}, + {ELEMENT: 8}, + {ELEMENT: 9}, + ]; + afterEach(function () { + sandbox.verifyAndRestore(); + }); + it('should be able to unlock device using pin (API level >= 21)', async function () { + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'stringKeyToArr').returns(keys); + sandbox.stub(driver.adb, 'getApiLevel').returns(21); + sandbox.stub(driver, 'findElOrEls') + .withExactArgs('id', 'com.android.systemui:id/digit_text', true) + .returns(els); + sandbox.stub(driver.adb, 'isScreenLocked').returns(true); + sandbox.stub(driver.adb, 'keyevent').withExactArgs(66).once(); + for (let e of els) { + sandbox.stub(driver, 'getAttribute') + .withExactArgs('text', e.ELEMENT) + .returns(e.ELEMENT.toString()); + } + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(UNLOCK_WAIT_TIME).twice(); + sandbox.stub(driver, 'click'); + + await pinUnlock.bind(driver)(caps); + + driver.click.getCall(0).args[0].should.equal(1); + driver.click.getCall(1).args[0].should.equal(3); + driver.click.getCall(2).args[0].should.equal(5); + driver.click.getCall(3).args[0].should.equal(7); + driver.click.getCall(4).args[0].should.equal(9); + }); + it('should be able to unlock device using pin (API level < 21)', async function () { + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'stringKeyToArr').returns(keys); + sandbox.stub(driver.adb, 'getApiLevel').returns(20); + for (let pin of keys) { + sandbox.stub(driver, 'findElOrEls') + .withExactArgs('id', `com.android.keyguard:id/key${pin}`, false) + .returns({ELEMENT: parseInt(pin, 10)}); + } + sandbox.stub(driver.adb, 'isScreenLocked').returns(false); + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); + sandbox.stub(driver, 'click'); + + await pinUnlock.bind(driver)(caps); + + driver.click.getCall(0).args[0].should.equal(1); + driver.click.getCall(1).args[0].should.equal(3); + driver.click.getCall(2).args[0].should.equal(5); + driver.click.getCall(3).args[0].should.equal(7); + driver.click.getCall(4).args[0].should.equal(9); + }); + it('should throw error if pin buttons does not exist (API level >= 21)', async function () { + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'stringKeyToArr').once(); + sandbox.stub(driver.adb, 'getApiLevel').returns(21); + sandbox.stub(driver, 'findElOrEls').returns(null); + sandbox.stub(unlockHelpers, 'pinUnlockWithKeyEvent').once(); + await pinUnlock.bind(driver)(caps); + }); + it('should throw error if pin buttons does not exist (API level < 21)', async function () { + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'stringKeyToArr').returns(keys); + sandbox.stub(driver.adb, 'getApiLevel').returns(20); + sandbox.stub(driver, 'findElOrEls') + .withExactArgs('id', 'com.android.keyguard:id/key1', false) + .returns(null); + sandbox.stub(unlockHelpers, 'pinUnlockWithKeyEvent').once(); + await pinUnlock.bind(driver)(caps); + }); + }); + describe('passwordUnlock', function() { + it('should be able to unlock device using password', async function () { + let caps = {unlockKey: 'psswrd'}; + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'encodePassword') + .withExactArgs(caps.unlockKey) + .returns(caps.unlockKey); + sandbox.stub(driver.adb, 'shell').withExactArgs(['input', 'text', caps.unlockKey]).once(); + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(INPUT_KEYS_WAIT_TIME).once(); + sandbox.stub(driver.adb, 'shell') + .withExactArgs(['input', 'keyevent', String(KEYCODE_NUMPAD_ENTER)]); + sandbox.stub(driver.adb, 'isScreenLocked').returns(true); + sandbox.stub(driver.adb, 'keyevent').withExactArgs(66).once(); + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(UNLOCK_WAIT_TIME).twice(); + await passwordUnlock.bind(driver)(caps); + }); + }); + describe('getPatternKeyPosition', function () { + it('should verify pattern pin is aproximatelly to its position', function () { + let pins = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(function mapPins(pin) { + return getPatternKeyPosition(pin, {x: 33, y: 323}, 137.6); }); - it('should throw an error is api is lower than 23 and trying to use fingerprintUnlock', async function () { - mocks.adb.expects('isScreenLocked').onCall(0).returns(true); - mocks.adb.expects('isScreenLocked').returns(false); - mocks.adb.expects('isLockManagementSupported').onCall(0).returns(false); - mocks.adb.expects('getApiLevel').once().returns(21); - await helpers - .unlock(helpers, adb, {unlockType: 'fingerprint', unlockKey: '1111'}) - .should.be.rejectedWith('Fingerprint'); - mocks.helpers.verify(); + let cols = [101, 238, 375]; + let rows = [391, 528, 665]; + chai.expect(pins[0].x).to.be.within(cols[0] - 5, cols[0] + 5); + chai.expect(pins[1].x).to.be.within(cols[1] - 5, cols[1] + 5); + chai.expect(pins[2].x).to.be.within(cols[2] - 5, cols[2] + 5); + chai.expect(pins[3].x).to.be.within(cols[0] - 5, cols[0] + 5); + chai.expect(pins[4].x).to.be.within(cols[1] - 5, cols[1] + 5); + chai.expect(pins[5].x).to.be.within(cols[2] - 5, cols[2] + 5); + chai.expect(pins[6].x).to.be.within(cols[0] - 5, cols[0] + 5); + chai.expect(pins[7].x).to.be.within(cols[1] - 5, cols[1] + 5); + chai.expect(pins[8].x).to.be.within(cols[2] - 5, cols[2] + 5); + chai.expect(pins[0].y).to.be.within(rows[0] - 5, rows[0] + 5); + chai.expect(pins[1].y).to.be.within(rows[0] - 5, rows[0] + 5); + chai.expect(pins[2].y).to.be.within(rows[0] - 5, rows[0] + 5); + chai.expect(pins[3].y).to.be.within(rows[1] - 5, rows[1] + 5); + chai.expect(pins[4].y).to.be.within(rows[1] - 5, rows[1] + 5); + chai.expect(pins[5].y).to.be.within(rows[1] - 5, rows[1] + 5); + chai.expect(pins[6].y).to.be.within(rows[2] - 5, rows[2] + 5); + chai.expect(pins[7].y).to.be.within(rows[2] - 5, rows[2] + 5); + chai.expect(pins[8].y).to.be.within(rows[2] - 5, rows[2] + 5); + }); + }); + describe('getPatternActions', function () { + it('should generate press, moveTo, relase gesture scheme to unlock by pattern', function () { + let keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9']; + let actions = getPatternActions(keys, {x: 0, y: 0}, 1); + actions.map((action, i) => { + if (i === 0) { + action.action.should.equal('press'); + } else if (i === keys.length) { + action.action.should.equal('release'); + } else { + action.action.should.equal('moveTo'); + } }); - }) - ); + }); + it('should verify pattern gestures moves to non consecutives pins', function () { + let keys = ['7', '2', '9', '8', '5', '6', '1', '4', '3']; + let actions = getPatternActions(keys, {x: 0, y: 0}, 1); + // Move from pin 7 to pin 2 + actions[1].options.x.should.equal(2); + actions[1].options.y.should.equal(1); + // Move from pin 2 to pin 9 + actions[2].options.x.should.equal(3); + actions[2].options.y.should.equal(3); + // Move from pin 9 to pin 8 + actions[3].options.x.should.equal(2); + actions[3].options.y.should.equal(3); + // Move from pin 8 to pin 5 + actions[4].options.x.should.equal(2); + actions[4].options.y.should.equal(2); + // Move from pin 5 to pin 6 + actions[5].options.x.should.equal(3); + actions[5].options.y.should.equal(2); + // Move from pin 6 to pin 1 + actions[6].options.x.should.equal(1); + actions[6].options.y.should.equal(1); + // Move from pin 1 to pin 4 + actions[7].options.x.should.equal(1); + actions[7].options.y.should.equal(2); + // Move from pin 4 to pin 3 + actions[8].options.x.should.equal(3); + actions[8].options.y.should.equal(1); + }); + }); + describe('patternUnlock', function () { + const el = {ELEMENT: 1}; + const pos = {x: 10, y: 20}; + const size = {width: 300}; + const keys = ['1', '3', '5', '7', '9']; + const caps = {unlockKey: '13579'}; + beforeEach(function () { + sandbox.stub(driver.adb, 'dismissKeyguard').once(); + sandbox.stub(unlockHelpers, 'stringKeyToArr').returns(keys); + sandbox.stub(driver, 'getLocation').withExactArgs(el.ELEMENT).returns(pos); + sandbox.stub(driver, 'getSize').withExactArgs(el.ELEMENT).returns(size); + sandbox.stub(unlockHelpers, 'getPatternActions').withExactArgs(keys, pos, 100).returns('actions'); + sandbox.stub(driver, 'performTouch').withExactArgs('actions').once(); + sandbox.stub(asyncboxHelpers, 'sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); + }); + it('should be able to unlock device using pattern (API level >= 21)', async function () { + sandbox.stub(driver.adb, 'getApiLevel').returns(21); + sandbox.stub(driver, 'findElOrEls') + .withExactArgs('id', 'com.android.systemui:id/lockPatternView', false) + .returns(el); + await patternUnlock.bind(driver)(caps); + }); + it('should be able to unlock device using pattern (API level < 21)', async function () { + sandbox.stub(driver.adb, 'getApiLevel').returns(20); + sandbox.stub(driver, 'findElOrEls') + .withExactArgs('id', 'com.android.keyguard:id/lockPatternView', false) + .returns(el); + await patternUnlock.bind(driver)(caps); + }); + }); }); diff --git a/test/unit/unlock-helper-specs.js b/test/unit/unlock-helper-specs.js deleted file mode 100644 index 42ef7304..00000000 --- a/test/unit/unlock-helper-specs.js +++ /dev/null @@ -1,351 +0,0 @@ -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {withMocks} from '@appium/test-support'; -import sinon from 'sinon'; -import {AndroidDriver} from '../../lib/driver'; -import * as asyncbox from 'asyncbox'; -import ADB from 'appium-adb'; - -const KEYCODE_NUMPAD_ENTER = 66; -const INPUT_KEYS_WAIT_TIME = 100; -const UNLOCK_WAIT_TIME = 100; - -chai.should(); -chai.use(chaiAsPromised); - -describe('Unlock Helpers', function () { - let adb = new ADB(); - let driver = new AndroidDriver(); - let sandbox = sinon.createSandbox(); - let expect = chai.expect; - describe('validateUnlockCapabilities', function () { - function toCaps(unlockType, unlockKey) { - return { - unlockType, - unlockKey, - }; - } - - it('should verify the unlock keys for pin/pinWithKeyEvent', function () { - for (const invalidValue of [undefined, ' ', '1abc']) { - expect(() => helpers.validateUnlockCapabilities(toCaps('pin', invalidValue))).to.throw; - expect(() => helpers.validateUnlockCapabilities(toCaps('pinWithKeyEvent', invalidValue))).to - .throw; - } - helpers.validateUnlockCapabilities(toCaps('pin', '1111')); - helpers.validateUnlockCapabilities(toCaps('pinWithKeyEvent', '1111')); - }); - it('should verify the unlock keys for fingerprint', function () { - for (const invalidValue of [undefined, ' ', '1abc']) { - expect(() => helpers.validateUnlockCapabilities(toCaps('fingerprint', invalidValue))).to - .throw; - } - helpers.validateUnlockCapabilities(toCaps('fingerprint', '1')); - }); - it('should verify the unlock keys for pattern', function () { - for (const invalidValue of [undefined, '1abc', '', '1', '1213', '01234', ' ']) { - expect(() => helpers.validateUnlockCapabilities(toCaps('pattern', invalidValue))).to.throw; - } - for (const validValue of ['1234', '123456789']) { - helpers.validateUnlockCapabilities(toCaps('pattern', validValue)); - } - }); - it('should verify the unlock keys for password', function () { - for (const invalidValue of [undefined, '123', ' ']) { - expect(() => helpers.validateUnlockCapabilities(toCaps('password', invalidValue))).to.throw; - } - for (const validValue of [ - '121c3', - 'appium', - 'appium-android-driver', - '@#$%&-+()*"\':;!?,_ ./~`|={}\\[]', - ]) { - helpers.validateUnlockCapabilities(toCaps('password', validValue)); - } - }); - it('should throw error if unlock type is invalid', function () { - expect(() => helpers.validateUnlockCapabilities(toCaps('invalid_unlock_type', '1'))).to.throw; - }); - }); - describe('encodePassword', function () { - it('should verify the password with blank space is encoded', function () { - helpers.encodePassword('a p p i u m').should.equal('a%sp%sp%si%su%sm'); - helpers.encodePassword(' ').should.equal('%s%s%s'); - }); - }); - describe('stringKeyToArr', function () { - it('should cast string keys to array', function () { - helpers.stringKeyToArr('1234').should.eql(['1', '2', '3', '4']); - helpers.stringKeyToArr(' 1234 ').should.eql(['1', '2', '3', '4']); - helpers.stringKeyToArr('1 2 3 4').should.eql(['1', '2', '3', '4']); - helpers.stringKeyToArr('1 2 3 4').should.eql(['1', '2', '3', '4']); - }); - }); - describe( - 'fingerprintUnlock', - withMocks({adb, asyncbox}, (mocks) => { - it('should be able to unlock device via fingerprint if API level >= 23', async function () { - let caps = {unlockKey: '123'}; - mocks.adb.expects('getApiLevel').returns(23); - mocks.adb.expects('fingerprint').withExactArgs(caps.unlockKey).once(); - mocks.asyncbox.expects('sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); - await helpers.fingerprintUnlock(adb, driver, caps).should.be.fulfilled; - mocks.adb.verify(); - mocks.asyncbox.verify(); - }); - it('should throw error if API level < 23', async function () { - mocks.adb.expects('getApiLevel').returns(22); - mocks.adb.expects('fingerprint').never(); - mocks.asyncbox.expects('sleep').never(); - await helpers - .fingerprintUnlock(adb) - .should.eventually.be.rejectedWith('only works for Android 6+'); - mocks.adb.verify(); - mocks.asyncbox.verify(); - }); - }) - ); - describe( - 'pinUnlock', - withMocks({adb, helpers, driver, asyncbox}, (mocks) => { - const caps = {unlockKey: '13579'}; - const keys = ['1', '3', '5', '7', '9']; - const els = [ - {ELEMENT: 1}, - {ELEMENT: 2}, - {ELEMENT: 3}, - {ELEMENT: 4}, - {ELEMENT: 5}, - {ELEMENT: 6}, - {ELEMENT: 7}, - {ELEMENT: 8}, - {ELEMENT: 9}, - ]; - afterEach(function () { - sandbox.restore(); - }); - it('should be able to unlock device using pin (API level >= 21)', async function () { - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers.expects('stringKeyToArr').returns(keys); - mocks.adb.expects('getApiLevel').returns(21); - mocks.driver - .expects('findElOrEls') - .withExactArgs('id', 'com.android.systemui:id/digit_text', true) - .returns(els); - mocks.adb.expects('isScreenLocked').returns(true); - mocks.adb.expects('keyevent').withExactArgs(66).once(); - for (let e of els) { - mocks.driver - .expects('getAttribute') - .withExactArgs('text', e.ELEMENT) - .returns(e.ELEMENT.toString()); - } - mocks.asyncbox.expects('sleep').withExactArgs(UNLOCK_WAIT_TIME).twice(); - sandbox.stub(driver, 'click'); - - await helpers.pinUnlock(adb, driver, caps); - - driver.click.getCall(0).args[0].should.equal(1); - driver.click.getCall(1).args[0].should.equal(3); - driver.click.getCall(2).args[0].should.equal(5); - driver.click.getCall(3).args[0].should.equal(7); - driver.click.getCall(4).args[0].should.equal(9); - - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.adb.verify(); - mocks.asyncbox.verify(); - }); - it('should be able to unlock device using pin (API level < 21)', async function () { - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers.expects('stringKeyToArr').returns(keys); - mocks.adb.expects('getApiLevel').returns(20); - for (let pin of keys) { - mocks.driver - .expects('findElOrEls') - .withExactArgs('id', `com.android.keyguard:id/key${pin}`, false) - .returns({ELEMENT: parseInt(pin, 10)}); - } - mocks.adb.expects('isScreenLocked').returns(false); - mocks.asyncbox.expects('sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); - sandbox.stub(driver, 'click'); - - await helpers.pinUnlock(adb, driver, caps); - - driver.click.getCall(0).args[0].should.equal(1); - driver.click.getCall(1).args[0].should.equal(3); - driver.click.getCall(2).args[0].should.equal(5); - driver.click.getCall(3).args[0].should.equal(7); - driver.click.getCall(4).args[0].should.equal(9); - - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.adb.verify(); - mocks.asyncbox.verify(); - }); - it('should throw error if pin buttons does not exist (API level >= 21)', async function () { - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers.expects('stringKeyToArr').once(); - mocks.adb.expects('getApiLevel').returns(21); - mocks.driver.expects('findElOrEls').returns(null); - mocks.helpers.expects('pinUnlockWithKeyEvent').once(); - await helpers.pinUnlock(adb, driver, caps); - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.adb.verify(); - }); - it('should throw error if pin buttons does not exist (API level < 21)', async function () { - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers.expects('stringKeyToArr').returns(keys); - mocks.adb.expects('getApiLevel').returns(20); - mocks.driver - .expects('findElOrEls') - .withExactArgs('id', 'com.android.keyguard:id/key1', false) - .returns(null); - mocks.helpers.expects('pinUnlockWithKeyEvent').once(); - await helpers.pinUnlock(adb, driver, caps); - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.adb.verify(); - }); - }) - ); - describe( - 'passwordUnlock', - withMocks({adb, helpers, driver, asyncbox}, (mocks) => { - it('should be able to unlock device using password', async function () { - let caps = {unlockKey: 'psswrd'}; - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers - .expects('encodePassword') - .withExactArgs(caps.unlockKey) - .returns(caps.unlockKey); - mocks.adb.expects('shell').withExactArgs(['input', 'text', caps.unlockKey]).once(); - mocks.asyncbox.expects('sleep').withExactArgs(INPUT_KEYS_WAIT_TIME).once(); - mocks.adb - .expects('shell') - .withExactArgs(['input', 'keyevent', String(KEYCODE_NUMPAD_ENTER)]); - mocks.adb.expects('isScreenLocked').returns(true); - mocks.adb.expects('keyevent').withExactArgs(66).once(); - mocks.asyncbox.expects('sleep').withExactArgs(UNLOCK_WAIT_TIME).twice(); - await helpers.passwordUnlock(adb, driver, caps); - mocks.helpers.verify(); - mocks.adb.verify(); - mocks.asyncbox.verify(); - }); - }) - ); - describe('getPatternKeyPosition', function () { - it('should verify pattern pin is aproximatelly to its position', function () { - let pins = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(function mapPins(pin) { - return helpers.getPatternKeyPosition(pin, {x: 33, y: 323}, 137.6); - }); - let cols = [101, 238, 375]; - let rows = [391, 528, 665]; - expect(pins[0].x).to.be.within(cols[0] - 5, cols[0] + 5); - expect(pins[1].x).to.be.within(cols[1] - 5, cols[1] + 5); - expect(pins[2].x).to.be.within(cols[2] - 5, cols[2] + 5); - expect(pins[3].x).to.be.within(cols[0] - 5, cols[0] + 5); - expect(pins[4].x).to.be.within(cols[1] - 5, cols[1] + 5); - expect(pins[5].x).to.be.within(cols[2] - 5, cols[2] + 5); - expect(pins[6].x).to.be.within(cols[0] - 5, cols[0] + 5); - expect(pins[7].x).to.be.within(cols[1] - 5, cols[1] + 5); - expect(pins[8].x).to.be.within(cols[2] - 5, cols[2] + 5); - expect(pins[0].y).to.be.within(rows[0] - 5, rows[0] + 5); - expect(pins[1].y).to.be.within(rows[0] - 5, rows[0] + 5); - expect(pins[2].y).to.be.within(rows[0] - 5, rows[0] + 5); - expect(pins[3].y).to.be.within(rows[1] - 5, rows[1] + 5); - expect(pins[4].y).to.be.within(rows[1] - 5, rows[1] + 5); - expect(pins[5].y).to.be.within(rows[1] - 5, rows[1] + 5); - expect(pins[6].y).to.be.within(rows[2] - 5, rows[2] + 5); - expect(pins[7].y).to.be.within(rows[2] - 5, rows[2] + 5); - expect(pins[8].y).to.be.within(rows[2] - 5, rows[2] + 5); - }); - }); - describe('getPatternActions', function () { - it('should generate press, moveTo, relase gesture scheme to unlock by pattern', function () { - let keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9']; - let actions = helpers.getPatternActions(keys, {x: 0, y: 0}, 1); - actions.map((action, i) => { - if (i === 0) { - action.action.should.equal('press'); - } else if (i === keys.length) { - action.action.should.equal('release'); - } else { - action.action.should.equal('moveTo'); - } - }); - }); - it('should verify pattern gestures moves to non consecutives pins', function () { - let keys = ['7', '2', '9', '8', '5', '6', '1', '4', '3']; - let actions = helpers.getPatternActions(keys, {x: 0, y: 0}, 1); - // Move from pin 7 to pin 2 - actions[1].options.x.should.equal(2); - actions[1].options.y.should.equal(1); - // Move from pin 2 to pin 9 - actions[2].options.x.should.equal(3); - actions[2].options.y.should.equal(3); - // Move from pin 9 to pin 8 - actions[3].options.x.should.equal(2); - actions[3].options.y.should.equal(3); - // Move from pin 8 to pin 5 - actions[4].options.x.should.equal(2); - actions[4].options.y.should.equal(2); - // Move from pin 5 to pin 6 - actions[5].options.x.should.equal(3); - actions[5].options.y.should.equal(2); - // Move from pin 6 to pin 1 - actions[6].options.x.should.equal(1); - actions[6].options.y.should.equal(1); - // Move from pin 1 to pin 4 - actions[7].options.x.should.equal(1); - actions[7].options.y.should.equal(2); - // Move from pin 4 to pin 3 - actions[8].options.x.should.equal(3); - actions[8].options.y.should.equal(1); - }); - }); - describe( - 'patternUnlock', - withMocks({driver, helpers, adb, asyncbox}, (mocks) => { - const el = {ELEMENT: 1}; - const pos = {x: 10, y: 20}; - const size = {width: 300}; - const keys = ['1', '3', '5', '7', '9']; - const caps = {unlockKey: '13579'}; - beforeEach(function () { - mocks.adb.expects('dismissKeyguard').once(); - mocks.helpers.expects('stringKeyToArr').returns(keys); - mocks.driver.expects('getLocation').withExactArgs(el.ELEMENT).returns(pos); - mocks.driver.expects('getSize').withExactArgs(el.ELEMENT).returns(size); - mocks.helpers.expects('getPatternActions').withExactArgs(keys, pos, 100).returns('actions'); - mocks.driver.expects('performTouch').withExactArgs('actions').once(); - mocks.asyncbox.expects('sleep').withExactArgs(UNLOCK_WAIT_TIME).once(); - }); - it('should be able to unlock device using pattern (API level >= 21)', async function () { - mocks.adb.expects('getApiLevel').returns(21); - mocks.driver - .expects('findElOrEls') - .withExactArgs('id', 'com.android.systemui:id/lockPatternView', false) - .returns(el); - await helpers.patternUnlock(adb, driver, caps); - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.asyncbox.verify(); - mocks.adb.verify(); - }); - it('should be able to unlock device using pattern (API level < 21)', async function () { - mocks.adb.expects('getApiLevel').returns(20); - mocks.driver - .expects('findElOrEls') - .withExactArgs('id', 'com.android.keyguard:id/lockPatternView', false) - .returns(el); - await helpers.patternUnlock(adb, driver, caps); - mocks.helpers.verify(); - mocks.driver.verify(); - mocks.asyncbox.verify(); - mocks.adb.verify(); - }); - }) - ); -});