diff --git a/packages/e2e/features/ui/components/liveness/disable-start-screen.feature b/packages/e2e/features/ui/components/liveness/disable-start-screen.feature index f9501b76611..c94a3699131 100644 --- a/packages/e2e/features/ui/components/liveness/disable-start-screen.feature +++ b/packages/e2e/features/ui/components/liveness/disable-start-screen.feature @@ -24,6 +24,3 @@ Feature: Disable Start Screen Then I click the "FaceMovementAndLightChallenge" selectfield and select the "FaceMovementChallenge" option Then I see "FaceMovementChallenge" Then I see "liveness-detector" element - Then I see the "Face didn't fit inside oval in time limit." timeout error - Then I click the "Try again" button - Then I see "Loading" diff --git a/packages/e2e/features/ui/components/liveness/no-light.feature b/packages/e2e/features/ui/components/liveness/no-light.feature index 0e2be1e9ff9..0c96c2aab63 100644 --- a/packages/e2e/features/ui/components/liveness/no-light.feature +++ b/packages/e2e/features/ui/components/liveness/no-light.feature @@ -23,7 +23,3 @@ Feature: Liveness Detector Then I see "connecting" Then I click the "Start video check" button Then I see "liveness-detector" element - Then I see "Move closer" - Then I see the "Face didn't fit inside oval in time limit." timeout error - Then I click the "Try again" button - Then I see the "Start video check" button diff --git a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/CameraSelector.tsx b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/CameraSelector.tsx new file mode 100644 index 00000000000..f68032db7d5 --- /dev/null +++ b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/CameraSelector.tsx @@ -0,0 +1,43 @@ +import { Flex, Label, SelectField, View } from '@aws-amplify/ui-react'; +import React from 'react'; +import { LivenessClassNames } from '../types/classNames'; + +interface CameraSelectorProps { + deviceId?: string; + onSelect: (e: React.ChangeEvent) => void; + devices: MediaDeviceInfo[]; +} + +export const CameraSelector = (props: CameraSelectorProps): JSX.Element => { + const { + onSelect: onCameraChange, + devices: selectableDevices, + deviceId: selectedDeviceId, + } = props; + return ( + + + + + {selectableDevices.map((device) => ( + + ))} + + + + ); +}; diff --git a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.tsx b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.tsx index 78c81de1958..81b3dbc9cd7 100644 --- a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.tsx +++ b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/LivenessCameraModule.tsx @@ -1,15 +1,7 @@ import React, { useState, useRef } from 'react'; import { classNames } from '@aws-amplify/ui'; -import { - Button, - Flex, - Label, - Loader, - SelectField, - Text, - View, -} from '@aws-amplify/ui-react'; +import { Button, Flex, Loader, Text, View } from '@aws-amplify/ui-react'; import { useColorMode } from '@aws-amplify/ui-react/internal'; import { FaceMatchState, clearOvalCanvas, drawStaticOval } from '../service'; import { @@ -40,6 +32,7 @@ import { DefaultRecordingIcon, } from '../shared/DefaultStartScreenComponents'; import { FACE_MOVEMENT_CHALLENGE } from '../service/utils/constants'; +import { CameraSelector } from './CameraSelector'; export const selectChallengeType = createLivenessSelector( (state) => state.context.parsedSessionInformation?.Challenge?.Name @@ -160,6 +153,13 @@ export const LivenessCameraModule = ( videoWidth && videoHeight ? videoWidth / videoHeight : 0 ); + // Only mobile device camera selection for no light challenge + const hasMultipleDevices = !!selectableDevices?.length; + const allowDeviceSelection = + isStartView && + hasMultipleDevices && + (!isMobileScreen || isFaceMovementChallenge); + React.useEffect(() => { if (canvasRef?.current && videoRef?.current && videoStream && isStartView) { drawStaticOval(canvasRef.current, videoRef.current, videoStream); @@ -417,38 +417,13 @@ export const LivenessCameraModule = ( - {isStartView && - !isMobileScreen && - selectableDevices && - selectableDevices.length > 1 && ( - - - - - {selectableDevices?.map((device) => ( - - ))} - - - - )} + {allowDeviceSelection ? ( + + ) : null} diff --git a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/CameraSelector.test.tsx b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/CameraSelector.test.tsx new file mode 100644 index 00000000000..3b84a890c99 --- /dev/null +++ b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/CameraSelector.test.tsx @@ -0,0 +1,21 @@ +import { render } from '@testing-library/react'; +import { CameraSelector } from '../CameraSelector'; +import React from 'react'; + +const mockMediaDevice: MediaDeviceInfo = { + deviceId: 'foobar', + groupId: 'foobar', + kind: 'videoinput', + label: 'foobar', + toJSON: jest.fn(), +}; + +describe('CameraSelector', () => { + it('should render', () => { + const result = render( + {}} devices={[mockMediaDevice]} /> + ); + + expect(result.container).toBeDefined(); + }); +}); diff --git a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/LivenessCameraModule.test.tsx b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/LivenessCameraModule.test.tsx index cd06cff9d51..9a16bc00b6a 100644 --- a/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/LivenessCameraModule.test.tsx +++ b/packages/react-liveness/src/components/FaceLivenessDetector/LivenessCheck/__tests__/LivenessCameraModule.test.tsx @@ -483,7 +483,9 @@ describe('LivenessCameraModule', () => { it('should render hair check screen when isStart = true', () => { isStart = true; mockStateMatchesAndSelectors(); - mockUseLivenessSelector.mockReturnValue(25).mockReturnValue(['device-id']); + mockUseLivenessSelector + .mockReturnValue(25) + .mockReturnValue(['device-id', 'device-id-2', 'device-id-3']); renderWithLivenessProvider( { videoEl.dispatchEvent(new Event('canplay')); expect(screen.getByTestId('popover-icon')).toBeInTheDocument(); + expect( + screen.getByTestId('amplify-liveness-camera-select') + ).toBeInTheDocument(); }); it('selectors should work', () => { diff --git a/packages/react-liveness/src/components/FaceLivenessDetector/utils/helpers.ts b/packages/react-liveness/src/components/FaceLivenessDetector/utils/helpers.ts index 815bc10608f..9fffdb0d07c 100644 --- a/packages/react-liveness/src/components/FaceLivenessDetector/utils/helpers.ts +++ b/packages/react-liveness/src/components/FaceLivenessDetector/utils/helpers.ts @@ -8,5 +8,4 @@ export const STATIC_VIDEO_CONSTRAINTS: MediaTrackConstraints = { ideal: 480, }, frameRate: { min: 15, ideal: 30, max: 30 }, - facingMode: 'user', };