diff --git a/.bundle/config b/.bundle/config index 848943bb527..7cc4a942be2 100644 --- a/.bundle/config +++ b/.bundle/config @@ -1,2 +1 @@ -BUNDLE_PATH: "vendor/bundle" BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/.detoxrc.js b/.detoxrc.js index 996da11b600..fcb90d2dc20 100644 --- a/.detoxrc.js +++ b/.detoxrc.js @@ -68,7 +68,7 @@ module.exports = { 'android.emulator': { type: 'android.emulator', device: { - avdName: 'Pixel_5_API_30', + avdName: 'Pixel_5_Pro_API_30', }, }, }, diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index db8294e3e5f..c7a4a415e4c 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -6,41 +6,41 @@ import { addParameters, addArgsEnhancer, clearDecorators, -} from "@storybook/react-native"; +} from '@storybook/react-native'; global.STORIES = [ { - titlePrefix: "", - directory: "./app/component-library/components", - files: "**/*.stories.?(ts|tsx|js|jsx)", + titlePrefix: '', + directory: './app/component-library/components', + files: '**/*.stories.?(ts|tsx|js|jsx)', importPathMatcher: - "^\\.[\\\\/](?:app\\/component-library\\/components(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", + '^\\.[\\\\/](?:app\\/component-library\\/components(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$', }, { - titlePrefix: "", - directory: "./app/component-library/base-components", - files: "**/*.stories.?(ts|tsx|js|jsx)", + titlePrefix: '', + directory: './app/component-library/base-components', + files: '**/*.stories.?(ts|tsx|js|jsx)', importPathMatcher: - "^\\.[\\\\/](?:app\\/component-library\\/base-components(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", + '^\\.[\\\\/](?:app\\/component-library\\/base-components(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$', }, { - titlePrefix: "", - directory: "./app/component-library/components-temp/TagColored", - files: "**/*.stories.?(ts|tsx|js|jsx)", + titlePrefix: '', + directory: './app/component-library/components-temp/TagColored', + files: '**/*.stories.?(ts|tsx|js|jsx)', importPathMatcher: - "^\\.[\\\\/](?:app\\/component-library\\/components-temp\\/TagColored(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", + '^\\.[\\\\/](?:app\\/component-library\\/components-temp\\/TagColored(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$', }, ]; -import "@storybook/addon-ondevice-controls/register"; +import '@storybook/addon-ondevice-controls/register'; -import { decorators, parameters } from "./preview"; +import { decorators, parameters } from './preview'; if (decorators) { if (__DEV__) { // stops the warning from showing on every HMR - require("react-native").LogBox.ignoreLogs([ - "`clearDecorators` is deprecated and will be removed in Storybook 7.0", + require('react-native').LogBox.ignoreLogs([ + '`clearDecorators` is deprecated and will be removed in Storybook 7.0', ]); } // workaround for global decorators getting infinitely applied on HMR, see https://github.com/storybookjs/react-native/issues/185 @@ -54,67 +54,67 @@ if (parameters) { const getStories = () => { return { - "./app/component-library/components/Accordions/Accordion/Accordion.stories.tsx": require("../app/component-library/components/Accordions/Accordion/Accordion.stories.tsx"), - "./app/component-library/components/Accordions/Accordion/foundation/AccordionHeader/AccordionHeader.stories.tsx": require("../app/component-library/components/Accordions/Accordion/foundation/AccordionHeader/AccordionHeader.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/Avatar.stories.tsx": require("../app/component-library/components/Avatars/Avatar/Avatar.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/variants/AvatarAccount/AvatarAccount.stories.tsx": require("../app/component-library/components/Avatars/Avatar/variants/AvatarAccount/AvatarAccount.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/variants/AvatarFavicon/AvatarFavicon.stories.tsx": require("../app/component-library/components/Avatars/Avatar/variants/AvatarFavicon/AvatarFavicon.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.stories.tsx": require("../app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.stories.tsx": require("../app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.stories.tsx"), - "./app/component-library/components/Avatars/Avatar/variants/AvatarToken/AvatarToken.stories.tsx": require("../app/component-library/components/Avatars/Avatar/variants/AvatarToken/AvatarToken.stories.tsx"), - "./app/component-library/components/Badges/Badge/Badge.stories.tsx": require("../app/component-library/components/Badges/Badge/Badge.stories.tsx"), - "./app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.stories.tsx": require("../app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.stories.tsx"), - "./app/component-library/components/Badges/Badge/variants/BadgeNotifications/BadgeNotifications.stories.tsx": require("../app/component-library/components/Badges/Badge/variants/BadgeNotifications/BadgeNotifications.stories.tsx"), - "./app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.stories.tsx": require("../app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.stories.tsx"), - "./app/component-library/components/Badges/BadgeWrapper/BadgeWrapper.stories.tsx": require("../app/component-library/components/Badges/BadgeWrapper/BadgeWrapper.stories.tsx"), - "./app/component-library/components/Banners/Banner/Banner.stories.tsx": require("../app/component-library/components/Banners/Banner/Banner.stories.tsx"), - "./app/component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert.stories.tsx": require("../app/component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert.stories.tsx"), - "./app/component-library/components/Banners/Banner/variants/BannerTip/BannerTip.stories.tsx": require("../app/component-library/components/Banners/Banner/variants/BannerTip/BannerTip.stories.tsx"), - "./app/component-library/components/BottomSheets/BottomSheet/BottomSheet.stories.tsx": require("../app/component-library/components/BottomSheets/BottomSheet/BottomSheet.stories.tsx"), - "./app/component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter.stories.tsx": require("../app/component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter.stories.tsx"), - "./app/component-library/components/BottomSheets/BottomSheetHeader/BottomSheetHeader.stories.tsx": require("../app/component-library/components/BottomSheets/BottomSheetHeader/BottomSheetHeader.stories.tsx"), - "./app/component-library/components/Buttons/Button/Button.stories.tsx": require("../app/component-library/components/Buttons/Button/Button.stories.tsx"), - "./app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.stories.tsx": require("../app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.stories.tsx"), - "./app/component-library/components/Buttons/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx": require("../app/component-library/components/Buttons/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx"), - "./app/component-library/components/Buttons/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx": require("../app/component-library/components/Buttons/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx"), - "./app/component-library/components/Buttons/ButtonIcon/ButtonIcon.stories.tsx": require("../app/component-library/components/Buttons/ButtonIcon/ButtonIcon.stories.tsx"), - "./app/component-library/components/Cards/Card/Card.stories.tsx": require("../app/component-library/components/Cards/Card/Card.stories.tsx"), - "./app/component-library/components/Cells/Cell/Cell.stories.tsx": require("../app/component-library/components/Cells/Cell/Cell.stories.tsx"), - "./app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.stories.tsx": require("../app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.stories.tsx"), - "./app/component-library/components/Cells/Cell/variants/CellMultiSelect/CellMultiSelect.stories.tsx": require("../app/component-library/components/Cells/Cell/variants/CellMultiSelect/CellMultiSelect.stories.tsx"), - "./app/component-library/components/Cells/Cell/variants/CellSelect/CellSelect.stories.tsx": require("../app/component-library/components/Cells/Cell/variants/CellSelect/CellSelect.stories.tsx"), - "./app/component-library/components/Checkbox/Checkbox.stories.tsx": require("../app/component-library/components/Checkbox/Checkbox.stories.tsx"), - "./app/component-library/components/Form/HelpText/HelpText.stories.tsx": require("../app/component-library/components/Form/HelpText/HelpText.stories.tsx"), - "./app/component-library/components/Form/Label/Label.stories.tsx": require("../app/component-library/components/Form/Label/Label.stories.tsx"), - "./app/component-library/components/Form/TextField/foundation/Input/Input.stories.tsx": require("../app/component-library/components/Form/TextField/foundation/Input/Input.stories.tsx"), - "./app/component-library/components/Form/TextField/TextField.stories.tsx": require("../app/component-library/components/Form/TextField/TextField.stories.tsx"), - "./app/component-library/components/Form/TextFieldSearch/TextFieldSearch.stories.tsx": require("../app/component-library/components/Form/TextFieldSearch/TextFieldSearch.stories.tsx"), - "./app/component-library/components/HeaderBase/HeaderBase.stories.tsx": require("../app/component-library/components/HeaderBase/HeaderBase.stories.tsx"), - "./app/component-library/components/Icons/Icon/Icon.stories.tsx": require("../app/component-library/components/Icons/Icon/Icon.stories.tsx"), - "./app/component-library/components/List/ListItem/ListItem.stories.tsx": require("../app/component-library/components/List/ListItem/ListItem.stories.tsx"), - "./app/component-library/components/List/ListItemMultiSelect/ListItemMultiSelect.stories.tsx": require("../app/component-library/components/List/ListItemMultiSelect/ListItemMultiSelect.stories.tsx"), - "./app/component-library/components/List/ListItemSelect/ListItemSelect.stories.tsx": require("../app/component-library/components/List/ListItemSelect/ListItemSelect.stories.tsx"), - "./app/component-library/components/Modals/ModalConfirmation/ModalConfirmation.stories.tsx": require("../app/component-library/components/Modals/ModalConfirmation/ModalConfirmation.stories.tsx"), - "./app/component-library/components/Modals/ModalMandatory/ModalMandatory.stories.tsx": require("../app/component-library/components/Modals/ModalMandatory/ModalMandatory.stories.tsx"), - "./app/component-library/components/Navigation/TabBar/TabBar.stories.tsx": require("../app/component-library/components/Navigation/TabBar/TabBar.stories.tsx"), - "./app/component-library/components/Navigation/TabBarItem/TabBarItem.stories.tsx": require("../app/component-library/components/Navigation/TabBarItem/TabBarItem.stories.tsx"), - "./app/component-library/components/Overlay/Overlay.stories.tsx": require("../app/component-library/components/Overlay/Overlay.stories.tsx"), - "./app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx": require("../app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx"), - "./app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx": require("../app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx"), - "./app/component-library/components/RadioButton/RadioButton.stories.tsx": require("../app/component-library/components/RadioButton/RadioButton.stories.tsx"), - "./app/component-library/components/Select/SelectButton/SelectButton.stories.tsx": require("../app/component-library/components/Select/SelectButton/SelectButton.stories.tsx"), - "./app/component-library/components/Select/SelectOption/SelectOption.stories.tsx": require("../app/component-library/components/Select/SelectOption/SelectOption.stories.tsx"), - "./app/component-library/components/Select/SelectValue/SelectValue.stories.tsx": require("../app/component-library/components/Select/SelectValue/SelectValue.stories.tsx"), - "./app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx": require("../app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx"), - "./app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx": require("../app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx"), - "./app/component-library/components/Tags/Tag/Tag.stories.tsx": require("../app/component-library/components/Tags/Tag/Tag.stories.tsx"), - "./app/component-library/components/Tags/TagUrl/TagUrl.stories.tsx": require("../app/component-library/components/Tags/TagUrl/TagUrl.stories.tsx"), - "./app/component-library/components/Texts/Text/Text.stories.tsx": require("../app/component-library/components/Texts/Text/Text.stories.tsx"), - "./app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx": require("../app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx"), - "./app/component-library/components/Toast/Toast.stories.tsx": require("../app/component-library/components/Toast/Toast.stories.tsx"), - "./app/component-library/base-components/TagBase/TagBase.stories.tsx": require("../app/component-library/base-components/TagBase/TagBase.stories.tsx"), - "./app/component-library/components-temp/TagColored/TagColored.stories.tsx": require("../app/component-library/components-temp/TagColored/TagColored.stories.tsx"), - "./app/components/UI/Name/Name.stories.tsx": require("../app/components/UI/Name/Name.stories.tsx"), + './app/component-library/components/Accordions/Accordion/Accordion.stories.tsx': require('../app/component-library/components/Accordions/Accordion/Accordion.stories.tsx'), + './app/component-library/components/Accordions/Accordion/foundation/AccordionHeader/AccordionHeader.stories.tsx': require('../app/component-library/components/Accordions/Accordion/foundation/AccordionHeader/AccordionHeader.stories.tsx'), + './app/component-library/components/Avatars/Avatar/Avatar.stories.tsx': require('../app/component-library/components/Avatars/Avatar/Avatar.stories.tsx'), + './app/component-library/components/Avatars/Avatar/variants/AvatarAccount/AvatarAccount.stories.tsx': require('../app/component-library/components/Avatars/Avatar/variants/AvatarAccount/AvatarAccount.stories.tsx'), + './app/component-library/components/Avatars/Avatar/variants/AvatarFavicon/AvatarFavicon.stories.tsx': require('../app/component-library/components/Avatars/Avatar/variants/AvatarFavicon/AvatarFavicon.stories.tsx'), + './app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.stories.tsx': require('../app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.stories.tsx'), + './app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.stories.tsx': require('../app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.stories.tsx'), + './app/component-library/components/Avatars/Avatar/variants/AvatarToken/AvatarToken.stories.tsx': require('../app/component-library/components/Avatars/Avatar/variants/AvatarToken/AvatarToken.stories.tsx'), + './app/component-library/components/Badges/Badge/Badge.stories.tsx': require('../app/component-library/components/Badges/Badge/Badge.stories.tsx'), + './app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.stories.tsx': require('../app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.stories.tsx'), + './app/component-library/components/Badges/Badge/variants/BadgeNotifications/BadgeNotifications.stories.tsx': require('../app/component-library/components/Badges/Badge/variants/BadgeNotifications/BadgeNotifications.stories.tsx'), + './app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.stories.tsx': require('../app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.stories.tsx'), + './app/component-library/components/Badges/BadgeWrapper/BadgeWrapper.stories.tsx': require('../app/component-library/components/Badges/BadgeWrapper/BadgeWrapper.stories.tsx'), + './app/component-library/components/Banners/Banner/Banner.stories.tsx': require('../app/component-library/components/Banners/Banner/Banner.stories.tsx'), + './app/component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert.stories.tsx': require('../app/component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert.stories.tsx'), + './app/component-library/components/Banners/Banner/variants/BannerTip/BannerTip.stories.tsx': require('../app/component-library/components/Banners/Banner/variants/BannerTip/BannerTip.stories.tsx'), + './app/component-library/components/BottomSheets/BottomSheet/BottomSheet.stories.tsx': require('../app/component-library/components/BottomSheets/BottomSheet/BottomSheet.stories.tsx'), + './app/component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter.stories.tsx': require('../app/component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter.stories.tsx'), + './app/component-library/components/BottomSheets/BottomSheetHeader/BottomSheetHeader.stories.tsx': require('../app/component-library/components/BottomSheets/BottomSheetHeader/BottomSheetHeader.stories.tsx'), + './app/component-library/components/Buttons/Button/Button.stories.tsx': require('../app/component-library/components/Buttons/Button/Button.stories.tsx'), + './app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.stories.tsx': require('../app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.stories.tsx'), + './app/component-library/components/Buttons/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx': require('../app/component-library/components/Buttons/Button/variants/ButtonPrimary/ButtonPrimary.stories.tsx'), + './app/component-library/components/Buttons/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx': require('../app/component-library/components/Buttons/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx'), + './app/component-library/components/Buttons/ButtonIcon/ButtonIcon.stories.tsx': require('../app/component-library/components/Buttons/ButtonIcon/ButtonIcon.stories.tsx'), + './app/component-library/components/Cards/Card/Card.stories.tsx': require('../app/component-library/components/Cards/Card/Card.stories.tsx'), + './app/component-library/components/Cells/Cell/Cell.stories.tsx': require('../app/component-library/components/Cells/Cell/Cell.stories.tsx'), + './app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.stories.tsx': require('../app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.stories.tsx'), + './app/component-library/components/Cells/Cell/variants/CellMultiSelect/CellMultiSelect.stories.tsx': require('../app/component-library/components/Cells/Cell/variants/CellMultiSelect/CellMultiSelect.stories.tsx'), + './app/component-library/components/Cells/Cell/variants/CellSelect/CellSelect.stories.tsx': require('../app/component-library/components/Cells/Cell/variants/CellSelect/CellSelect.stories.tsx'), + './app/component-library/components/Checkbox/Checkbox.stories.tsx': require('../app/component-library/components/Checkbox/Checkbox.stories.tsx'), + './app/component-library/components/Form/HelpText/HelpText.stories.tsx': require('../app/component-library/components/Form/HelpText/HelpText.stories.tsx'), + './app/component-library/components/Form/Label/Label.stories.tsx': require('../app/component-library/components/Form/Label/Label.stories.tsx'), + './app/component-library/components/Form/TextField/foundation/Input/Input.stories.tsx': require('../app/component-library/components/Form/TextField/foundation/Input/Input.stories.tsx'), + './app/component-library/components/Form/TextField/TextField.stories.tsx': require('../app/component-library/components/Form/TextField/TextField.stories.tsx'), + './app/component-library/components/Form/TextFieldSearch/TextFieldSearch.stories.tsx': require('../app/component-library/components/Form/TextFieldSearch/TextFieldSearch.stories.tsx'), + './app/component-library/components/HeaderBase/HeaderBase.stories.tsx': require('../app/component-library/components/HeaderBase/HeaderBase.stories.tsx'), + './app/component-library/components/Icons/Icon/Icon.stories.tsx': require('../app/component-library/components/Icons/Icon/Icon.stories.tsx'), + './app/component-library/components/List/ListItem/ListItem.stories.tsx': require('../app/component-library/components/List/ListItem/ListItem.stories.tsx'), + './app/component-library/components/List/ListItemMultiSelect/ListItemMultiSelect.stories.tsx': require('../app/component-library/components/List/ListItemMultiSelect/ListItemMultiSelect.stories.tsx'), + './app/component-library/components/List/ListItemSelect/ListItemSelect.stories.tsx': require('../app/component-library/components/List/ListItemSelect/ListItemSelect.stories.tsx'), + './app/component-library/components/Modals/ModalConfirmation/ModalConfirmation.stories.tsx': require('../app/component-library/components/Modals/ModalConfirmation/ModalConfirmation.stories.tsx'), + './app/component-library/components/Modals/ModalMandatory/ModalMandatory.stories.tsx': require('../app/component-library/components/Modals/ModalMandatory/ModalMandatory.stories.tsx'), + './app/component-library/components/Navigation/TabBar/TabBar.stories.tsx': require('../app/component-library/components/Navigation/TabBar/TabBar.stories.tsx'), + './app/component-library/components/Navigation/TabBarItem/TabBarItem.stories.tsx': require('../app/component-library/components/Navigation/TabBarItem/TabBarItem.stories.tsx'), + './app/component-library/components/Overlay/Overlay.stories.tsx': require('../app/component-library/components/Overlay/Overlay.stories.tsx'), + './app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx': require('../app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx'), + './app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx': require('../app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx'), + './app/component-library/components/RadioButton/RadioButton.stories.tsx': require('../app/component-library/components/RadioButton/RadioButton.stories.tsx'), + './app/component-library/components/Select/SelectButton/SelectButton.stories.tsx': require('../app/component-library/components/Select/SelectButton/SelectButton.stories.tsx'), + './app/component-library/components/Select/SelectOption/SelectOption.stories.tsx': require('../app/component-library/components/Select/SelectOption/SelectOption.stories.tsx'), + './app/component-library/components/Select/SelectValue/SelectValue.stories.tsx': require('../app/component-library/components/Select/SelectValue/SelectValue.stories.tsx'), + './app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx': require('../app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx'), + './app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx': require('../app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx'), + './app/component-library/components/Tags/Tag/Tag.stories.tsx': require('../app/component-library/components/Tags/Tag/Tag.stories.tsx'), + './app/component-library/components/Tags/TagUrl/TagUrl.stories.tsx': require('../app/component-library/components/Tags/TagUrl/TagUrl.stories.tsx'), + './app/component-library/components/Texts/Text/Text.stories.tsx': require('../app/component-library/components/Texts/Text/Text.stories.tsx'), + './app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx': require('../app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx'), + './app/component-library/components/Toast/Toast.stories.tsx': require('../app/component-library/components/Toast/Toast.stories.tsx'), + './app/component-library/base-components/TagBase/TagBase.stories.tsx': require('../app/component-library/base-components/TagBase/TagBase.stories.tsx'), + './app/component-library/components-temp/TagColored/TagColored.stories.tsx': require('../app/component-library/components-temp/TagColored/TagColored.stories.tsx'), + './app/components/UI/Name/Name.stories.tsx': require('../app/components/UI/Name/Name.stories.tsx'), }; }; diff --git a/Gemfile b/Gemfile index 4b44d2caec4..6043d03d5c9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,10 @@ source 'https://rubygems.org' -# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby '>= 3.0.0' +# Recommended to use http://rbenv.org/ to install and use this version +ruby '>= 3.1.4' +# Allow minor version updates up to but excluding 2.0.0 gem 'cocoapods', '~> 1.12' + +# Allow all version updates up to but excluding 7.1.0 gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index c5a8dc43d63..85a73654725 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,26 +1,29 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.0.7.2) + activesupport (7.0.8.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.4) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) + base64 (0.2.0) claide (1.1.0) - cocoapods (1.12.0) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.12.0) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.6.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -32,8 +35,8 @@ GEM molinillo (~> 0.8.0) nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.12.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -44,7 +47,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -53,31 +56,32 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.15.5) + ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) - json (2.5.1) - minitest (5.20.0) + json (2.7.2) + minitest (5.22.3) molinillo (0.8.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.2.5) + rexml (3.2.6) ruby-macho (2.5.1) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.22.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -87,13 +91,15 @@ GEM PLATFORMS arm64-darwin-22 + ruby x86_64-linux DEPENDENCIES - cocoapods (>= 1.11.3) + activesupport (>= 6.1.7.3, < 7.1.0) + cocoapods (~> 1.12) RUBY VERSION - ruby 3.0.0p0 + ruby 3.1.4p223 BUNDLED WITH - 2.2.3 + 2.5.8 diff --git a/android/app/build.gradle b/android/app/build.gradle index 666001ab65b..373711b1f99 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -308,7 +308,7 @@ dependencies { } else { implementation jscFlavor } - androidTestImplementation('com.wix:detox:+') { + androidTestImplementation('com.wix:detox-legacy:+') { exclude module: "protobuf-lite" } androidTestImplementation ('androidx.test.espresso:espresso-contrib:3.4.0') diff --git a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants.ts b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants.ts index 9837323000f..b83dad70e7e 100644 --- a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants.ts +++ b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.constants.ts @@ -5,7 +5,7 @@ import { ImageSourcePropType, Platform } from 'react-native'; // External dependencies. import { AvatarSize } from '../../Avatar.types'; -import { BrowserViewSelectorsIDs } from '../../../../../../../e2e/selectors/BrowserView.selectors'; +import { BrowserViewSelectorsIDs } from '../../../../../../../e2e/selectors/Browser/BrowserView.selectors'; import generateTestId from '../../../../../../../wdio/utils/generateTestId'; // Internal dependencies. diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts index 914d20be1f8..4059c710cc9 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts @@ -5,13 +5,6 @@ import { FlatListProps } from 'react-native'; // External dependencies. import { Account, UseAccounts } from '../../hooks/useAccounts'; -export interface SelectedAccount { - address: string; - lastUsed?: number; -} - -export type SelectedAccountByAddress = Record; - /** * AccountSelectorList props. */ diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx index c73e5ff4c0f..9daea63b352 100644 --- a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx @@ -14,7 +14,7 @@ import Text from '../../../component-library/components/Texts/Text'; import { BrowserUrlBarProps } from './BrowserUrlBar.types'; import stylesheet from './BrowserUrlBar.styles'; -import { BrowserViewSelectorsIDs } from '../../../../e2e/selectors/BrowserView.selectors'; +import { BrowserViewSelectorsIDs } from '../../../../e2e/selectors/Browser/BrowserView.selectors'; import Url from 'url-parse'; import { regex } from '../../../../app/util/regex'; diff --git a/app/components/UI/Name/Name.stories.tsx b/app/components/UI/Name/Name.stories.tsx index ba84f79a8b0..80299be1843 100644 --- a/app/components/UI/Name/Name.stories.tsx +++ b/app/components/UI/Name/Name.stories.tsx @@ -7,7 +7,12 @@ import { default as NameComponent } from './Name'; import { NameProperties, NameType } from './Name.types'; const backdropStyle = { backgroundColor: 'white', padding: 50 }; -const ADDRESS_1 = '0x2990079bcdEe240329a520d2444386FC119da21a'; +const UNKNOWN_ADDRESS = '0x2990079bcdEe240329a520d2444386FC119da21a'; +const METAMASK_MAINNET_BRIDGE_ADDRESS = + '0x0439e60F02a8900a951603950d8D4527f400C3f1'; +const NFT_ADDRESS = '0x12345643338A158DeC2FbF411B71aeB188743'; +const SELECTED_CHAIN_ID = '0x1'; +const SELECTED_ACCOUNT = '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42'; type Story = StoryObj; @@ -15,6 +20,32 @@ const storeMock = configureStore({ reducer: (state) => state, preloadedState: { settings: { useBlockieIcon: false }, + engine: { + backgroundState: { + NetworkController: { + providerConfig: { + chainId: SELECTED_CHAIN_ID, + }, + }, + PreferencesController: { + selectedAddress: SELECTED_ACCOUNT, + }, + NftController: { + allNfts: {}, + allNftContracts: { + [SELECTED_ACCOUNT]: { + [SELECTED_CHAIN_ID]: [ + { + address: NFT_ADDRESS, + name: 'MyToken', + symbol: 'MTK', + }, + ], + }, + }, + }, + }, + }, }, }); @@ -32,14 +63,31 @@ const meta: Meta = { export default meta; export const UnknownAddress: Story = { - args: { type: NameType.EthereumAddress, value: ADDRESS_1 }, + args: { type: NameType.EthereumAddress, value: UNKNOWN_ADDRESS }, +}; + +export const RecognizedFirstPartyContract: Story = { + args: { + type: NameType.EthereumAddress, + value: METAMASK_MAINNET_BRIDGE_ADDRESS, + }, +}; + +export const RecognizedNFT: Story = { + args: { + type: NameType.EthereumAddress, + value: NFT_ADDRESS, + }, }; export const NarrowWidth: Story = { render() { return ( - + ); }, diff --git a/app/components/UI/Ramp/hooks/useActivationKeys.test.ts b/app/components/UI/Ramp/hooks/useActivationKeys.test.ts new file mode 100644 index 00000000000..87ab9833489 --- /dev/null +++ b/app/components/UI/Ramp/hooks/useActivationKeys.test.ts @@ -0,0 +1,238 @@ +import { act, waitFor } from '@testing-library/react-native'; +import useActivationKeys from './useActivationKeys'; +import { SDK } from '../sdk'; +import { renderHookWithProvider } from '../../../../util/test/renderWithProvider'; +import initialRootState from '../../../../util/test/initial-root-state'; +import { ActivationKey } from '../../../../reducers/fiatOrders/types'; + +jest.mock('../sdk', () => ({ + SDK: { + getActivationKeys: jest.fn().mockReturnValue([]), + setActivationKeys: jest.fn(), + }, +})); + +function renderHookWithActivationKeys( + hook: () => ReturnType, + activationKeys: ActivationKey[] = [], +) { + return renderHookWithProvider(hook, { + state: { + ...initialRootState, + fiatOrders: { + ...initialRootState.fiatOrders, + activationKeys, + }, + }, + }); +} + +const mockStoredKeys = [ + { + key: 'key1', + active: true, + }, + { + key: 'key2', + active: false, + }, + { + key: 'key3', + active: true, + }, +]; + +describe('useActivationKeys', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call SDK.setActivationKeys with active stored keys and return them', async () => { + const storedKeys = [...mockStoredKeys]; + + const activeStoredKeys = storedKeys.filter(({ active }) => active); + const activeStoredKeysNames = activeStoredKeys.map(({ key }) => key); + ( + SDK.getActivationKeys as jest.MockedFunction + ).mockReturnValueOnce(activeStoredKeysNames); + + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: true, + }), + storedKeys, + ); + + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + + expect(SDK.setActivationKeys).toHaveBeenCalledWith(activeStoredKeysNames); + expect(result.current.activationKeys).toEqual(storedKeys); + }); + + it('should early return if options.internal is false and return inactive keys', async () => { + const storedKeys = [...mockStoredKeys]; + + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: false, + }), + storedKeys, + ); + + expect(SDK.setActivationKeys).not.toHaveBeenCalled(); + expect(SDK.getActivationKeys).not.toHaveBeenCalled(); + expect( + (result.current.activationKeys as ActivationKey[]).every( + ({ active }) => !active, + ), + ).toBe(true); + }); + + it('should early return if options is empty and return inactive keys', async () => { + const storedKeys = [...mockStoredKeys]; + + const { result } = renderHookWithActivationKeys( + () => useActivationKeys(), + storedKeys, + ); + + expect(SDK.setActivationKeys).not.toHaveBeenCalled(); + expect(SDK.getActivationKeys).not.toHaveBeenCalled(); + expect( + (result.current.activationKeys as ActivationKey[]).every( + ({ active }) => !active, + ), + ).toBe(true); + }); + + it('should add and set activation key when calling removeActivationKey', async () => { + const storedKeys = [...mockStoredKeys]; + const activeStoredKeysNames = storedKeys + .filter(({ active }) => active) + .map(({ key }) => key); + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: true, + }), + storedKeys, + ); + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + + act(() => { + result.current.addActivationKey('key4'); + }); + + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + expect(SDK.setActivationKeys).toHaveBeenLastCalledWith([ + ...activeStoredKeysNames, + 'key4', + ]); + + /** We expect all keys to be false because we did not mock + * `SDK.getActivationKeys` to return the SDK keys array. + * It is currently returning `[]`, marking al stored keys as inactive + */ + expect(result.current.activationKeys).toEqual([ + ...storedKeys.map((key) => ({ ...key, active: false })), + { + key: 'key4', + active: false, + }, + ]); + }); + + it('should remove activation key when calling addActivationKey', async () => { + const storedKeys = [...mockStoredKeys]; + const activeStoredKeysNames = storedKeys + .filter(({ active }) => active) + .map(({ key }) => key); + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: true, + }), + storedKeys, + ); + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + + act(() => { + result.current.removeActivationKey('key3'); + }); + + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + expect(SDK.setActivationKeys).toHaveBeenLastCalledWith([ + ...activeStoredKeysNames.filter((key) => key !== 'key3'), + ]); + + /** We expect all keys to be false because we did not mock + * `SDK.getActivationKeys` to return the SDK keys array. + * It is currently returning `[]`, marking al stored keys as inactive + */ + expect(result.current.activationKeys).toEqual([ + ...storedKeys + .filter(({ key }) => key !== 'key3') + .map((activationKey) => ({ ...activationKey, active: false })), + ]); + }); + + it('should update activation key when calling updateActivationKey', async () => { + const storedKeys = [...mockStoredKeys]; + const activeStoredKeysNames = storedKeys + .filter(({ active }) => active) + .map(({ key }) => key); + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: true, + }), + storedKeys, + ); + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + + act(() => { + result.current.updateActivationKey('key3', false); + }); + + waitFor(() => expect(result.current.isLoadingKeys).toBe(false)); + expect(SDK.setActivationKeys).toHaveBeenLastCalledWith([ + ...activeStoredKeysNames.filter((key) => key !== 'key3'), + ]); + + /** We expect all keys to be false because we did not mock + * `SDK.getActivationKeys` to return the SDK keys array. + * It is currently returning `[]`, marking al stored keys as inactive + */ + expect(result.current.activationKeys).toEqual([ + ...storedKeys.map((activationKey) => ({ + ...activationKey, + active: false, + })), + ]); + }); + + it('should call SDK.setActivationKeys with active stored only once if is provider instance and ignore function calls', async () => { + const storedKeys = [...mockStoredKeys]; + + const activeStoredKeys = storedKeys.filter(({ active }) => active); + const activeStoredKeysNames = activeStoredKeys.map(({ key }) => key); + ( + SDK.getActivationKeys as jest.MockedFunction + ).mockReturnValueOnce(activeStoredKeysNames); + + const { result } = renderHookWithActivationKeys( + () => + useActivationKeys({ + internal: true, + provider: true, + }), + storedKeys, + ); + + expect(SDK.setActivationKeys).toHaveBeenCalledTimes(1); + expect(SDK.setActivationKeys).toHaveBeenCalledWith(activeStoredKeysNames); + expect(result.current.activationKeys).toEqual(storedKeys); + }); +}); diff --git a/app/components/UI/Ramp/hooks/useActivationKeys.ts b/app/components/UI/Ramp/hooks/useActivationKeys.ts index cc5129b81c3..eaa71d2da3d 100644 --- a/app/components/UI/Ramp/hooks/useActivationKeys.ts +++ b/app/components/UI/Ramp/hooks/useActivationKeys.ts @@ -13,25 +13,21 @@ interface Options { internal?: boolean; } -export default function useActivationKeys( - options: Options = { - provider: false, - internal: false, - }, -) { +export default function useActivationKeys(options: Options = {}) { + const { internal = false, provider = false } = options; const dispatch = useDispatch(); const [sdkActivationKeys, setSdkActivationKeys] = useState(() => - SDK.getActivationKeys(), + internal ? SDK.getActivationKeys() : [], ); const deviceActivationKeys = useSelector(getActivationKeys); const [isLoadingKeys, setIsLoadingKeys] = useState(true); const [providerInitialized, setProviderInitialized] = useState(false); useEffect(() => { - if (!options.internal) { + if (!internal) { return; } - if (options.provider && providerInitialized) { + if (provider && providerInitialized) { return; } (async () => { @@ -44,16 +40,11 @@ export default function useActivationKeys( const sdkKeys = SDK.getActivationKeys(); setSdkActivationKeys(sdkKeys); setIsLoadingKeys(false); - if (options?.provider) { + if (provider) { setProviderInitialized(true); } })(); - }, [ - deviceActivationKeys, - options.internal, - options.provider, - providerInitialized, - ]); + }, [deviceActivationKeys, internal, provider, providerInitialized]); const activationKeys = useMemo(() => { const keys = deviceActivationKeys.map((activationKey) => ({ diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index cd4338161d2..d1b7b3c2ff6 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -23,7 +23,6 @@ import { ToastVariants, } from '../../../component-library/components/Toast'; import { ToastOptions } from '../../../component-library/components/Toast/Toast.types'; -import { SelectedAccount } from '../../../components/UI/AccountSelectorList/AccountSelectorList.types'; import { USER_INTENT } from '../../../constants/permissions'; import { MetaMetricsEvents } from '../../../core/Analytics'; import UntypedEngine from '../../../core/Engine'; @@ -289,20 +288,17 @@ const AccountConnect = (props: AccountConnectProps) => { ); const handleConnect = useCallback(async () => { - const selectedAccounts: SelectedAccount[] = selectedAddresses.map( - (address, index) => ({ address, lastUsed: Date.now() - index }), - ); const request = { ...hostInfo, metadata: { ...hostInfo.metadata, origin: channelIdOrHostname, }, - approvedAccounts: selectedAccounts, + approvedAccounts: selectedAddresses, }; - const connectedAccountLength = selectedAccounts.length; - const activeAddress = selectedAccounts[0].address; + const connectedAccountLength = selectedAddresses.length; + const activeAddress = selectedAddresses[0]; const activeAccountName = getAccountNameWithENS({ accountAddress: activeAddress, accounts, diff --git a/app/components/Views/AddBookmark/index.js b/app/components/Views/AddBookmark/index.js index 9853e53d627..a950f6e0547 100644 --- a/app/components/Views/AddBookmark/index.js +++ b/app/components/Views/AddBookmark/index.js @@ -7,7 +7,7 @@ import ActionView from '../../UI/ActionView'; import { getNavigationOptionsTitle } from '../../UI/Navbar'; import { ThemeContext, mockTheme } from '../../../util/theme'; -import { AddBookmarkViewSelectorsIDs } from '../../../../e2e/selectors/AddBookmarkView.selectors'; +import { AddBookmarkViewSelectorsIDs } from '../../../../e2e/selectors/Browser/AddBookmarkView.selectors'; const createStyles = (colors) => StyleSheet.create({ diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index be96aabb413..27084da288f 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -47,7 +47,7 @@ import { addBookmark } from '../../../actions/bookmarks'; import { addToHistory, addToWhitelist } from '../../../actions/browser'; import Device from '../../../util/device'; import AppConstants from '../../../core/AppConstants'; -import SearchApi from 'react-native-search-api'; +import SearchApi from '@metamask/react-native-search-api'; import { MetaMetricsEvents } from '../../../core/Analytics'; import setOnboardingWizardStep from '../../../actions/wizard'; import OnboardingWizard from '../../UI/OnboardingWizard'; @@ -97,7 +97,7 @@ import CLText from '../../../component-library/components/Texts/Text/Text'; import { TextVariant } from '../../../component-library/components/Texts/Text'; import { regex } from '../../../../app/util/regex'; import { selectChainId } from '../../../selectors/networkController'; -import { BrowserViewSelectorsIDs } from '../../../../e2e/selectors/BrowserView.selectors'; +import { BrowserViewSelectorsIDs } from '../../../../e2e/selectors/Browser/BrowserView.selectors'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { trackDappViewedEvent } from '../../../util/metrics'; import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; @@ -1524,7 +1524,7 @@ export const BrowserTab = (props) => { javascriptEnabled allowsInlineMediaPlayback useWebkit - testID={BrowserViewSelectorsIDs.ANDROID_CONTAINER} + testID={BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID} applicationNameForUserAgent={'WebView MetaMaskMobile'} onFileDownload={handleOnFileDownload} /> diff --git a/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx b/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx index 29876d965d4..1fb7c3c604c 100644 --- a/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx +++ b/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx @@ -5,7 +5,6 @@ import { TouchableOpacity, TextInput, InteractionManager, - Platform, } from 'react-native'; import ReusableModal, { ReusableModalRef } from '../../UI/ReusableModal'; import Icon from 'react-native-vector-icons/FontAwesome'; @@ -20,13 +19,8 @@ import { } from '../../../util/navigation/navUtils'; import Routes from '../../../constants/navigation/Routes'; import Device from '../../../util/device'; -import generateTestId from '../../../../wdio/utils/generateTestId'; -import { - URL_CLEAR_ICON, - URL_INPUT_BOX_ID, - CANCEL_BUTTON_ON_BROWSER_ID, -} from '../../../../wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds'; +import { BrowserURLBarSelectorsIDs } from '../../../../e2e/selectors/Browser/BrowserURLBar.selectors'; export interface BrowserUrlParams { onUrlInputSubmit: (inputValue: string | undefined) => void; url: string | undefined; @@ -90,7 +84,7 @@ const BrowserUrlModal = () => { ref={inputRef} autoCapitalize="none" autoCorrect={false} - {...generateTestId(Platform, URL_INPUT_BOX_ID)} + testID={BrowserURLBarSelectorsIDs.URL_INPUT} onChangeText={setAutocompleteValue} onSubmitEditing={() => triggerOnSubmit(autocompleteValue || '')} placeholder={strings('autocomplete.placeholder')} @@ -106,19 +100,15 @@ const BrowserUrlModal = () => { - + ) : null} diff --git a/app/components/Views/BrowserUrlModal/__snapshots__/BrowserUrlModal.test.tsx.snap b/app/components/Views/BrowserUrlModal/__snapshots__/BrowserUrlModal.test.tsx.snap index 0429e88facf..52f1a0e6725 100644 --- a/app/components/Views/BrowserUrlModal/__snapshots__/BrowserUrlModal.test.tsx.snap +++ b/app/components/Views/BrowserUrlModal/__snapshots__/BrowserUrlModal.test.tsx.snap @@ -54,7 +54,7 @@ exports[`BrowserUrlModal should render correctly 1`] = ` "paddingLeft": 15, } } - testID="url-input" + testID="browser-modal-url-input" /> ({ + __esModule: true, + default: jest.fn(), +})); jest.mock('./useFirstPartyContractName', () => ({ useFirstPartyContractName: jest.fn(), })); describe('useDisplayName', () => { + const mockUseWatchedNFTName = useWatchedNFTName as jest.MockedFunction< + typeof useWatchedNFTName + >; const mockUseFirstPartyContractName = useFirstPartyContractName as jest.MockedFunction< typeof useFirstPartyContractName @@ -41,6 +50,20 @@ describe('useDisplayName', () => { KNOWN_FIRST_PARTY_CONTRACT_NAME, ); + const displayName = useDisplayName( + NameType.EthereumAddress, + KNOWN_NFT_ADDRESS_CHECKSUMMED, + NETWORKS_CHAIN_ID.MAINNET, + ); + expect(displayName).toEqual({ + variant: DisplayNameVariant.Recognized, + name: KNOWN_FIRST_PARTY_CONTRACT_NAME, + }); + }); + + it('should return watched nft name', () => { + mockUseWatchedNFTName.mockReturnValue(KNOWN_NFT_NAME_MOCK); + const displayName = useDisplayName( NameType.EthereumAddress, KNOWN_NFT_ADDRESS_CHECKSUMMED, @@ -54,7 +77,7 @@ describe('useDisplayName', () => { expect(displayName).toEqual({ variant: DisplayNameVariant.Recognized, - name: KNOWN_FIRST_PARTY_CONTRACT_NAME, + name: KNOWN_NFT_NAME_MOCK, }); }); }); diff --git a/app/components/hooks/DisplayName/useDisplayName.ts b/app/components/hooks/DisplayName/useDisplayName.ts index bf3251365dc..de6a95eee69 100644 --- a/app/components/hooks/DisplayName/useDisplayName.ts +++ b/app/components/hooks/DisplayName/useDisplayName.ts @@ -1,6 +1,7 @@ import { Hex } from '@metamask/utils'; import { NameType } from '../../UI/Name/Name.types'; import { useFirstPartyContractName } from './useFirstPartyContractName'; +import useWatchedNFTName from './useWatchedNFTName'; /** * Indicate the source and nature of a display name for a given address. @@ -52,15 +53,18 @@ const useDisplayName: ( ) => DisplayName = (_type, value, chainId) => { const normalizedValue = value.toLowerCase(); + const watchedNftName = useWatchedNFTName(normalizedValue); const firstPartyContractName = useFirstPartyContractName( normalizedValue, chainId, ); - if (firstPartyContractName) { + const recognizedName = watchedNftName || firstPartyContractName; + + if (recognizedName) { return { variant: DisplayNameVariant.Recognized, - name: firstPartyContractName, + name: recognizedName, }; } diff --git a/app/components/hooks/DisplayName/useWatchedNFTName.test.ts b/app/components/hooks/DisplayName/useWatchedNFTName.test.ts new file mode 100644 index 00000000000..04ae2d5342e --- /dev/null +++ b/app/components/hooks/DisplayName/useWatchedNFTName.test.ts @@ -0,0 +1,43 @@ +import useWatchedNFTName from './useWatchedNFTName'; +import { collectibleContractsSelector } from '../../../reducers/collectibles'; + +const KNOWN_NFT_ADDRESS_CHECKSUMMED = + '0x495f947276749Ce646f68AC8c248420045cb7b5e'; +const KNOWN_NFT_NAME_MOCK = 'Known NFT'; +const COLLECTIBLES_MOCK = [ + { + name: KNOWN_NFT_NAME_MOCK, + address: KNOWN_NFT_ADDRESS_CHECKSUMMED, + }, +]; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn().mockImplementation((callback) => callback()), +})); + +jest.mock('../../../reducers/collectibles', () => ({ + collectibleContractsSelector: jest.fn(), +})); + +describe('useWatchedNFTName', () => { + const normalizedAddress = KNOWN_NFT_ADDRESS_CHECKSUMMED.toLowerCase(); + const collectibleContractsSelectorMock = jest.mocked( + collectibleContractsSelector, + ); + + beforeAll(() => { + collectibleContractsSelectorMock.mockReturnValue(COLLECTIBLES_MOCK); + }); + + it('returns null if no NFT matched', () => { + const name = useWatchedNFTName(normalizedAddress); + expect(name).toEqual(KNOWN_NFT_NAME_MOCK); + }); + + it('returns null if name no NFT matched', () => { + collectibleContractsSelectorMock.mockReturnValue([]); + const name = useWatchedNFTName(normalizedAddress); + expect(name).toEqual(null); + }); +}); diff --git a/app/components/hooks/DisplayName/useWatchedNFTName.ts b/app/components/hooks/DisplayName/useWatchedNFTName.ts new file mode 100644 index 00000000000..61f8bde98d6 --- /dev/null +++ b/app/components/hooks/DisplayName/useWatchedNFTName.ts @@ -0,0 +1,21 @@ +import { useSelector } from 'react-redux'; +import { NftContract } from '@metamask/assets-controllers'; +import { collectibleContractsSelector } from '../../../reducers/collectibles'; + +/** + * Get the name for the given NFT Address for the current chain. + * + * @param address The address of the NFT. + */ +const useWatchedNFTName: (address: string) => string | null | undefined = ( + address, +) => { + const watchedNfts: NftContract[] = useSelector(collectibleContractsSelector); + const watchedNft = watchedNfts.find( + (nft) => nft.address.toLowerCase() === address, + ); + + return watchedNft?.name ?? null; +}; + +export default useWatchedNFTName; diff --git a/app/constants/network.js b/app/constants/network.js index c5a18372d70..436e9e6eab7 100644 --- a/app/constants/network.js +++ b/app/constants/network.js @@ -36,6 +36,7 @@ export const NETWORKS_CHAIN_ID = { ARBITRUM_GOERLI: toHex('421613'), OPTIMISM_GOERLI: toHex('420'), MUMBAI: toHex('80001'), + OPBNB: toHex('204'), }; // To add a deprecation warning to a network, add it to the array diff --git a/app/core/BackgroundBridge/BackgroundBridge.js b/app/core/BackgroundBridge/BackgroundBridge.js index 44ab276a807..5fa7411be1a 100644 --- a/app/core/BackgroundBridge/BackgroundBridge.js +++ b/app/core/BackgroundBridge/BackgroundBridge.js @@ -385,7 +385,13 @@ export class BackgroundBridge extends EventEmitter { // filter and subscription polyfills engine.push(filterMiddleware); engine.push(subscriptionManager.middleware); - // watch asset + + // Add PermissionController middleware + engine.push( + Engine.context.PermissionController.createPermissionMiddleware({ + origin, + }), + ); ///: BEGIN:ONLY_INCLUDE_IF(snaps) // Snaps middleware diff --git a/app/core/Engine.ts b/app/core/Engine.ts index d22d7c73f07..e5ac006c503 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -26,6 +26,7 @@ import { } from '@metamask/assets-controllers'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) import { AppState } from 'react-native'; +import PREINSTALLED_SNAPS from '../lib/snaps/preinstalled-snaps'; ///: END:ONLY_INCLUDE_IF import { AddressBookController, @@ -528,6 +529,17 @@ class Engine { }, ); + const loggingController = new LoggingController({ + messenger: this.controllerMessenger.getRestricted< + 'LoggingController', + never, + never + >({ + name: 'LoggingController', + }), + state: initialState.LoggingController, + }); + const accountsControllerMessenger = this.controllerMessenger.getRestricted({ name: 'AccountsController', allowedEvents: [ @@ -825,8 +837,7 @@ class Engine { getInternalAccounts: accountsController.listAccounts.bind(accountsController), }), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error Typecast permissionType from getPermissionSpecifications to be of type PermissionType.RestrictedMethod permissionSpecifications: { ...getPermissionSpecifications({ getAllAccounts: () => keyringController.getAccounts(), @@ -1007,6 +1018,8 @@ class Engine { allowLocal: allowLocalSnaps, fetch: fetchFunction, }), + //@ts-expect-error types need to be aligned with snaps-controllers + preinstalledSnaps: PREINSTALLED_SNAPS, }); ///: END:ONLY_INCLUDE_IF @@ -1187,7 +1200,8 @@ class Engine { | 'ApprovalController:addRequest' | 'KeyringController:signPersonalMessage' | 'KeyringController:signMessage' - | 'KeyringController:signTypedMessage', + | 'KeyringController:signTypedMessage' + | 'LoggingController:add', never >({ name: 'SignatureController', @@ -1196,6 +1210,7 @@ class Engine { `${keyringController.name}:signPersonalMessage`, `${keyringController.name}:signMessage`, `${keyringController.name}:signTypedMessage`, + `${loggingController.name}:add`, ], }), isEthSignEnabled: () => @@ -1205,17 +1220,7 @@ class Engine { getAllState: () => store.getState(), getCurrentChainId: () => networkController.state.providerConfig.chainId, }), - new LoggingController({ - // @ts-expect-error TODO: Resolve/patch mismatch between base-controller versions. Before: never, never. Now: string, string, which expects 3rd and 4th args to be informed for restrictedControllerMessengers - messenger: this.controllerMessenger.getRestricted< - 'LoggingController', - never, - never - >({ - name: 'LoggingController', - }), - state: initialState.LoggingController, - }), + loggingController, ///: BEGIN:ONLY_INCLUDE_IF(snaps) snapController, subjectMetadataController, diff --git a/app/core/Permissions/index.ts b/app/core/Permissions/index.ts index 13ebf1f85e3..113ac824253 100644 --- a/app/core/Permissions/index.ts +++ b/app/core/Permissions/index.ts @@ -1,9 +1,8 @@ import { errorCodes as rpcErrorCodes } from '@metamask/rpc-errors'; -import { orderBy } from 'lodash'; import { RestrictedMethods, CaveatTypes } from './constants'; import ImportedEngine from '../Engine'; -import { SelectedAccount } from '../../components/UI/AccountSelectorList/AccountSelectorList.types'; import Logger from '../../util/Logger'; +import { getUniqueList } from '../../util/general'; const Engine = ImportedEngine as any; function getAccountsCaveatFromPermission(accountsPermission: any = {}) { @@ -39,9 +38,7 @@ export const getPermittedAccountsByHostname = ( (acc: any, subjectKey) => { const accounts = getAccountsFromSubject(subjects[subjectKey]); if (accounts.length > 0) { - acc[subjectKey] = accounts.map( - ({ address }: { address: string }) => address, - ); + acc[subjectKey] = accounts; } return acc; }, @@ -53,28 +50,32 @@ export const getPermittedAccountsByHostname = ( export const switchActiveAccounts = (hostname: string, accAddress: string) => { const { PermissionController } = Engine.context; - const existingAccounts: SelectedAccount[] = PermissionController.getCaveat( - hostname, - RestrictedMethods.eth_accounts, - CaveatTypes.restrictReturnedAccounts, - ).value; - const accountIndex = existingAccounts.findIndex( - ({ address }) => address === accAddress, + const existingPermittedAccountAddresses: string[] = + PermissionController.getCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + ).value; + const accountIndex = existingPermittedAccountAddresses.findIndex( + (address) => address === accAddress, ); if (accountIndex === -1) { throw new Error( `eth_accounts permission for hostname "${hostname}" does not permit "${accAddress} account".`, ); } - let newAccounts = [...existingAccounts]; - newAccounts.splice(accountIndex, 1); - newAccounts = [{ address: accAddress, lastUsed: Date.now() }, ...newAccounts]; + let newPermittedAccountAddresses = [...existingPermittedAccountAddresses]; + newPermittedAccountAddresses.splice(accountIndex, 1); + newPermittedAccountAddresses = getUniqueList([ + accAddress, + ...newPermittedAccountAddresses, + ]); PermissionController.updateCaveat( hostname, RestrictedMethods.eth_accounts, CaveatTypes.restrictReturnedAccounts, - newAccounts, + newPermittedAccountAddresses, ); }; @@ -88,36 +89,32 @@ export const addPermittedAccounts = ( RestrictedMethods.eth_accounts, CaveatTypes.restrictReturnedAccounts, ); - const existingPermittedAccountAddresses = existing.value.map( - ({ address }: { address: string }) => address, - ); + const existingPermittedAccountAddresses: string[] = existing.value; - for (const address in addresses) { - if (existingPermittedAccountAddresses.includes(address)) { - throw new Error( - `eth_accounts permission for hostname "${hostname}" already permits account "${address}".`, - ); - } - } - - const selectedAccounts: SelectedAccount[] = addresses.map( - (address, index) => ({ address, lastUsed: Date.now() - index }), + const newPermittedAccountsAddresses = getUniqueList( + addresses, + existingPermittedAccountAddresses, ); - const newSortedAccounts = orderBy( - [...existing.value, ...selectedAccounts], - 'lastUsed', - 'desc', - ); + // No change in permitted account addresses + if ( + newPermittedAccountsAddresses.length === + existingPermittedAccountAddresses.length + ) { + console.error( + `eth_accounts permission for hostname: (${hostname}) already exists for account addresses: (${existingPermittedAccountAddresses}).`, + ); + return existingPermittedAccountAddresses[0]; + } PermissionController.updateCaveat( hostname, RestrictedMethods.eth_accounts, CaveatTypes.restrictReturnedAccounts, - newSortedAccounts, + newPermittedAccountsAddresses, ); - return newSortedAccounts[0].address; + return newPermittedAccountsAddresses[0]; }; export const removePermittedAccounts = ( @@ -131,7 +128,7 @@ export const removePermittedAccounts = ( CaveatTypes.restrictReturnedAccounts, ); const remainingAccounts = existing.value.filter( - ({ address }: { address: string }) => !accounts.includes(address), + (address: string) => !accounts.includes(address), ); if (remainingAccounts.length === 0) { @@ -174,14 +171,12 @@ export const getPermittedAccounts = async ( hostname: string, ): Promise => { try { - const accountsWithLastUsed = + const accounts = await Engine.context.PermissionController.executeRestrictedMethod( hostname, RestrictedMethods.eth_accounts, ); - return accountsWithLastUsed.map(({ address }: { address: string }) => - address.toLowerCase(), - ); + return accounts; } catch (error: any) { if (error.code === rpcErrorCodes.provider.unauthorized) { return []; diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index 45a237ab76e..cd07bb00c3b 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -60,7 +60,7 @@ export const getCaveatSpecifications = ({ getInternalAccounts }) => ({ decorator: (method, caveat) => async (args) => { const allAccounts = await method(args); - const res = caveat.value.filter(({ address }) => { + const res = caveat.value.filter((address) => { const addressToCompare = address.toLowerCase(); return allAccounts.includes(addressToCompare); }); @@ -203,8 +203,7 @@ function validateCaveatAccounts(accounts, getInternalAccounts) { } const internalAccounts = getInternalAccounts(); - accounts.forEach((account) => { - const address = account?.address; + accounts.forEach((address) => { if (!address || typeof address !== 'string') { throw new Error( `${PermissionKeys.eth_accounts} error: Expected an array of objects that contains an Ethereum addresses. Received: "${address}".`, @@ -234,8 +233,6 @@ function validateCaveatAccounts(accounts, getInternalAccounts) { export const unrestrictedMethods = Object.freeze([ 'eth_blockNumber', 'eth_call', - 'eth_chainId', - 'eth_coinbase', 'eth_decrypt', 'eth_estimateGas', 'eth_feeHistory', @@ -252,9 +249,6 @@ export const unrestrictedMethods = Object.freeze([ 'eth_getLogs', 'eth_getProof', 'eth_getStorageAt', - 'eth_getTransactionByBlockHashAndIndex', - 'eth_getTransactionByBlockNumberAndIndex', - 'eth_getTransactionByHash', 'eth_getTransactionCount', 'eth_getTransactionReceipt', 'eth_getUncleByBlockHashAndIndex', @@ -262,31 +256,50 @@ export const unrestrictedMethods = Object.freeze([ 'eth_getUncleCountByBlockHash', 'eth_getUncleCountByBlockNumber', 'eth_getWork', - 'eth_hashrate', - 'eth_mining', 'eth_newBlockFilter', 'eth_newFilter', 'eth_newPendingTransactionFilter', 'eth_protocolVersion', 'eth_sendRawTransaction', - 'eth_sendTransaction', - 'eth_sign', - 'eth_signTypedData', 'eth_signTypedData_v1', - 'eth_signTypedData_v3', - 'eth_signTypedData_v4', 'eth_submitHashrate', 'eth_submitWork', 'eth_syncing', 'eth_uninstallFilter', - 'metamask_getProviderState', 'metamask_watchAsset', - 'net_listening', 'net_peerCount', + 'web3_sha3', + // Define unrestricted methods below to bypass PermissionController. These are eventually handled by RPCMethodMiddleware (User facing RPC methods) + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'eth_getTransactionByHash', + 'eth_getTransactionByBlockHashAndIndex', + 'eth_getTransactionByBlockNumberAndIndex', + 'eth_chainId', + 'eth_hashrate', + 'eth_mining', + 'net_listening', 'net_version', - 'personal_ecRecover', + 'eth_requestAccounts', + 'eth_coinbase', + 'parity_defaultAccount', + 'eth_sendTransaction', + 'eth_signTransaction', + 'eth_sign', 'personal_sign', - 'wallet_watchAsset', + 'personal_ecRecover', + 'parity_checkRequest', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', 'web3_clientVersion', - 'web3_sha3', + 'wallet_scanQRCode', + 'wallet_watchAsset', + 'metamask_removeFavorite', + 'metamask_showTutorial', + 'metamask_showAutocomplete', + 'metamask_injectHomepageScripts', + 'metamask_getProviderState', + 'metamask_logWeb3ShimUsage', + 'wallet_switchEthereumChain', ]); diff --git a/app/core/Permissions/specifications.test.js b/app/core/Permissions/specifications.test.js index 3252fd322b9..df07bb7d491 100644 --- a/app/core/Permissions/specifications.test.js +++ b/app/core/Permissions/specifications.test.js @@ -66,11 +66,7 @@ describe('PermissionController specifications', () => { describe('decorator', () => { it('returns array members included in the caveat value', async () => { const getIdentities = jest.fn(); - const caveatValues = [ - { address: '0x1', lastUsed: '1' }, - { address: '0x2', lastUsed: '2' }, - { address: '0x3', lastUsed: '3' }, - ]; + const caveatValues = ['0x1', '0x2', '0x3']; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -90,7 +86,7 @@ describe('PermissionController specifications', () => { it('returns an empty array if no array members are included in the caveat value', async () => { const getIdentities = jest.fn(); - const caveatValues = [{ address: '0x5', lastUsed: '1' }]; + const caveatValues = ['0x5']; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -106,10 +102,7 @@ describe('PermissionController specifications', () => { it('returns an empty array if the method result is an empty array', async () => { const getIdentities = jest.fn(); - const caveatValues = [ - { address: '0x1', lastUsed: '1' }, - { address: '0x2', lastUsed: '2' }, - ]; + const caveatValues = ['0x1', '0x2']; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -178,11 +171,7 @@ describe('PermissionController specifications', () => { ...baseEoaAccount, }, ]); - const caveatValues = [ - { address: '0x1', lastUsed: '1' }, - { address: '0x2', lastUsed: '2' }, - { address: '0x3', lastUsed: '3' }, - ]; + const caveatValues = ['0x1', '0x2', '0x3']; const { validator } = getCaveatSpecifications({ getInternalAccounts, diff --git a/app/core/RPCMethods/RPCMethodMiddleware.test.ts b/app/core/RPCMethods/RPCMethodMiddleware.test.ts index 3716d81f1ac..5e8eb9af4a4 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.test.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.test.ts @@ -15,12 +15,22 @@ import { getPermittedAccounts } from '../Permissions'; import { RPC } from '../../constants/network'; import { getRpcMethodMiddleware } from './RPCMethodMiddleware'; import AppConstants from '../AppConstants'; -import { PermissionConstraint } from '@metamask/permission-controller'; +import { + PermissionConstraint, + PermissionController, +} from '@metamask/permission-controller'; import PPOMUtil from '../../lib/ppom/ppom-util'; import initialBackgroundState from '../../util/test/initial-background-state.json'; import { Store } from 'redux'; import { RootState } from 'app/reducers'; import { addTransaction } from '../../util/transaction-controller'; +import { ControllerMessenger } from '@metamask/base-controller'; +import { + getCaveatSpecifications, + getPermissionSpecifications, + unrestrictedMethods, +} from '../Permissions/specifications'; +import { EthAccountType, EthMethod } from '@metamask/keyring-api'; jest.mock('../Engine', () => ({ context: { @@ -286,7 +296,7 @@ function setupSignature() { } describe('getRpcMethodMiddleware', () => { - it('allows unrecognized methods to pass through', async () => { + it('allows unrecognized methods to pass through without PermissionController middleware', async () => { const engine = new JsonRpcEngine(); const middleware = getRpcMethodMiddleware(getMinimalOptions()); engine.push(middleware); @@ -308,6 +318,123 @@ describe('getRpcMethodMiddleware', () => { expect(response.result).toBe('success'); }); + describe('with permission middleware before', () => { + const engine = new JsonRpcEngine(); + const controllerMessenger = new ControllerMessenger(); + const baseEoaAccount = { + type: EthAccountType.Eoa, + options: {}, + methods: [ + EthMethod.PersonalSign, + EthMethod.Sign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV1, + EthMethod.SignTypedDataV3, + EthMethod.SignTypedDataV4, + ], + }; + const mockGetInternalAccounts = jest.fn().mockImplementationOnce(() => [ + { + address: '0x1', + id: '21066553-d8c8-4cdc-af33-efc921cd3ca9', + metadata: { + name: 'Test Account 1', + lastSelected: 1, + keyring: { + type: 'HD Key Tree', + }, + }, + ...baseEoaAccount, + }, + ]); + const permissionController = new PermissionController({ + messenger: controllerMessenger.getRestricted({ + name: 'PermissionController', + }), + caveatSpecifications: getCaveatSpecifications({ + getInternalAccounts: mockGetInternalAccounts, + }), + // @ts-expect-error Typecast permissionType from getPermissionSpecifications to be of type PermissionType.RestrictedMethod + permissionSpecifications: { + ...getPermissionSpecifications({ + getAllAccounts: jest.fn().mockImplementation(async () => ['0x1']), + getInternalAccounts: mockGetInternalAccounts, + captureKeyringTypesWithMissingIdentities: jest + .fn() + .mockImplementation(() => []), + }), + }, + unrestrictedMethods, + }); + const permissionMiddleware = + permissionController.createPermissionMiddleware({ + origin: hostMock, + }); + // @ts-expect-error JsonRpcId (number | string | void) doesn't match PS middleware's id, which is (string | number | null) + engine.push(permissionMiddleware); + const middleware = getRpcMethodMiddleware(getMinimalOptions()); + engine.push(middleware); + + it('returns method not found error', async () => { + const fakeMethodName = 'this-is-a-fake-method'; + const response = await engine.handle({ + jsonrpc, + id: 1, + method: fakeMethodName, + }); + + const expectedError = rpcErrors.methodNotFound( + `The method "${fakeMethodName}" does not exist / is not available.`, + ); + + expect((response as JsonRpcFailure).error.code).toBe(expectedError.code); + expect((response as JsonRpcFailure).error.message).toBe( + expectedError.message, + ); + }); + + it('returns unauthorized error on restricted method without permission', async () => { + const ethAccountsMethodName = 'eth_accounts'; + const response = await engine.handle({ + jsonrpc, + id: 1, + method: ethAccountsMethodName, + }); + + const expectedError = providerErrors.unauthorized( + 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions', + ); + + expect((response as JsonRpcFailure).error.code).toBe(expectedError.code); + expect((response as JsonRpcFailure).error.message).toBe( + expectedError.message, + ); + }); + + it('handles restricted method with permission', async () => { + const ethAccountsMethodName = 'eth_accounts'; + permissionController.grantPermissions({ + subject: { origin: hostMock }, + approvedPermissions: { + eth_accounts: {}, + }, + requestData: { + approvedAccounts: ['0x1'], + }, + }); + const response = await engine.handle({ + jsonrpc, + id: 1, + method: ethAccountsMethodName, + }); + + expect((response as JsonRpcFailure).error).toBeUndefined(); + expect((response as JsonRpcSuccess).result).toStrictEqual([ + '0x1', + ]); + }); + }); + const accountMethods = [ 'eth_accounts', 'eth_coinbase', diff --git a/app/core/SDKConnect/SDKDeeplinkProtocol/DeeplinkProtocolService.ts b/app/core/SDKConnect/SDKDeeplinkProtocol/DeeplinkProtocolService.ts index f506581c178..18910b55c67 100644 --- a/app/core/SDKConnect/SDKDeeplinkProtocol/DeeplinkProtocolService.ts +++ b/app/core/SDKConnect/SDKDeeplinkProtocol/DeeplinkProtocolService.ts @@ -556,12 +556,8 @@ export default class DeeplinkProtocolService { this.currentClientId ?? '', ); - const connectedAddresses = ( - permissions?.eth_accounts?.caveats?.[0]?.value as { - address: string; - lastUsed: number; - }[] - )?.map((caveat) => caveat.address); + const connectedAddresses = permissions?.eth_accounts?.caveats?.[0] + ?.value as string[]; DevLogger.log( `DeeplinkProtocolService::clients_connected connectedAddresses`, diff --git a/app/core/WalletConnect/extractApprovedAccounts.ts b/app/core/WalletConnect/extractApprovedAccounts.ts index 53b616eaee9..c9a08017ec3 100644 --- a/app/core/WalletConnect/extractApprovedAccounts.ts +++ b/app/core/WalletConnect/extractApprovedAccounts.ts @@ -7,20 +7,9 @@ export const extractApprovedAccounts = ( | undefined, ) => { const approvedAccounts = accountPermission?.caveats - ?.map((restrictedAccount) => { - if (Array.isArray(restrictedAccount?.value)) { - return restrictedAccount.value - .map((account) => { - if ( - typeof account === 'object' && - account && - 'address' in account - ) { - return account.address; - } - return undefined; - }) - .flat(); + ?.map((caveat) => { + if (Array.isArray(caveat?.value)) { + return caveat.value; } return undefined; }) diff --git a/app/lib/snaps/preinstalled-snaps.ts b/app/lib/snaps/preinstalled-snaps.ts new file mode 100644 index 00000000000..0a014c350c2 --- /dev/null +++ b/app/lib/snaps/preinstalled-snaps.ts @@ -0,0 +1,8 @@ +import type { PreinstalledSnap } from '@metamask/snaps-controllers'; +import MessageSigningSnap from '@metamask/message-signing-snap/dist/preinstalled-snap.json'; + +const PREINSTALLED_SNAPS: readonly PreinstalledSnap[] = Object.freeze([ + MessageSigningSnap as PreinstalledSnap, +]); + +export default PREINSTALLED_SNAPS; diff --git a/app/reducers/collectibles/index.test.ts b/app/reducers/collectibles/index.test.ts index 924f8ad6d53..5f5efe118a5 100644 --- a/app/reducers/collectibles/index.test.ts +++ b/app/reducers/collectibles/index.test.ts @@ -13,7 +13,7 @@ const collectibleB2 = { tokenId: '102', address: '0xB' }; const selectedAddressA = '0x0A'; const selectedAddressB = '0x0B'; -describe('swaps reducer', () => { +describe('collectibles reducer', () => { it('should add favorite', () => { const initalState = reducer(undefined, emptyAction); const firstState = reducer(initalState, { diff --git a/app/store/migrations/041.test.ts b/app/store/migrations/041.test.ts new file mode 100644 index 00000000000..d0482f32411 --- /dev/null +++ b/app/store/migrations/041.test.ts @@ -0,0 +1,126 @@ +import migration from './041'; +import { merge } from 'lodash'; +import initialRootState from '../../util/test/initial-root-state'; +import { captureException } from '@sentry/react-native'; + +const oldState = { + engine: { + backgroundState: { + PermissionController: { + subjects: { + 'app.uniswap.org': { + origin: 'app.uniswap.org', + permissions: { + eth_accounts: { + id: 'ukrFhz7_z1gbog3mWNIoA', + parentCapability: 'eth_accounts', + invoker: 'app.uniswap.org', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + { + address: '0x04B09A749Bc6a1C111de308694Ba1Cd74A698523', + lastUsed: 1714709418122, + }, + ], + }, + ], + date: 1714709418138, + }, + }, + }, + }, + }, + }, + }, +}; + +const expectedNewState = { + engine: { + backgroundState: { + PermissionController: { + subjects: { + 'app.uniswap.org': { + origin: 'app.uniswap.org', + permissions: { + eth_accounts: { + id: 'ukrFhz7_z1gbog3mWNIoA', + parentCapability: 'eth_accounts', + invoker: 'app.uniswap.org', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0x04B09A749Bc6a1C111de308694Ba1Cd74A698523'], + }, + ], + date: 1714709418138, + }, + }, + }, + }, + }, + }, + }, +}; + +jest.mock('@sentry/react-native', () => ({ + captureException: jest.fn(), +})); +const mockedCaptureException = jest.mocked(captureException); + +describe('Migration #41', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + const invalidStates = [ + { + state: merge({}, initialRootState, { + engine: null, + }), + errorMessage: "Migration 41: Invalid engine state error: 'object'", + scenario: 'engine state is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: null, + }, + }), + errorMessage: + "Migration 41: Invalid engine backgroundState error: 'object'", + scenario: 'backgroundState is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { + PermissionController: null, + }, + }, + }), + errorMessage: + "Migration 41: Invalid PermissionController state error: 'null'", + scenario: 'PermissionController state is invalid', + }, + ]; + + for (const { errorMessage, scenario, state } of invalidStates) { + it(`should capture exception if ${scenario}`, async () => { + const newState = await migration(state); + + expect(newState).toStrictEqual(state); + expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error)); + expect(mockedCaptureException.mock.calls[0][0].message).toBe( + errorMessage, + ); + }); + } + + it('should update caveat values to resemble an array of addresses', async () => { + const newState = await migration(oldState); + expect(newState).toStrictEqual(expectedNewState); + }); +}); diff --git a/app/store/migrations/041.ts b/app/store/migrations/041.ts new file mode 100644 index 00000000000..c28df188fe3 --- /dev/null +++ b/app/store/migrations/041.ts @@ -0,0 +1,58 @@ +import { captureException } from '@sentry/react-native'; +import { hasProperty, isObject } from '@metamask/utils'; +import { ensureValidState } from './util'; + +/** + * Migration to remove metadata from Permissioned accounts + * + * @param state Persisted Redux state + * @returns + */ +export default function migrate(state: unknown) { + if (!ensureValidState(state, 41)) { + return state; + } + + const permissionControllerState = + state.engine.backgroundState.PermissionController; + + if (!isObject(permissionControllerState)) { + captureException( + new Error( + `Migration 41: Invalid PermissionController state error: '${JSON.stringify( + permissionControllerState, + )}'`, + ), + ); + return state; + } + + if ( + hasProperty(permissionControllerState, 'subjects') && + isObject(permissionControllerState.subjects) + ) { + for (const origin in permissionControllerState.subjects) { + const subject = permissionControllerState.subjects[origin]; + if (isObject(subject) && hasProperty(subject, 'permissions')) { + const permissions = subject.permissions; + if (isObject(permissions) && hasProperty(permissions, 'eth_accounts')) { + const ethAccounts = permissions.eth_accounts; + if ( + isObject(ethAccounts) && + hasProperty(ethAccounts, 'caveats') && + Array.isArray(ethAccounts.caveats) + ) { + ethAccounts.caveats = ethAccounts.caveats.map((caveat) => ({ + ...caveat, + value: caveat.value.map( + ({ address }: { address: string }) => address, + ), + })); + } + } + } + } + } + + return state; +} diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index c6811fb1338..9619ffecbf4 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -41,6 +41,7 @@ import migration37 from './037'; import migration38 from './038'; import migration39 from './039'; import migration40 from './040'; +import migration41 from './041'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -94,6 +95,7 @@ export const migrationList: MigrationsList = { 38: migration38, 39: migration39, 40: migration40, + 41: migration41, }; // Enable both synchronous and asynchronous migrations diff --git a/app/util/general/index.js b/app/util/general/index.js index 577e9dd7bfe..29d0ec72d9b 100644 --- a/app/util/general/index.js +++ b/app/util/general/index.js @@ -160,3 +160,33 @@ export const deepJSONParse = ({ jsonString, skipNumbers = true }) => { return parsedObject; }; + +/** + * Generates an array of referentially unique items from a list of arrays. + * + * @param {...Array} arrays - A list of arrays + * @returns {Array} - Returns a flattened array with unique items + * @throws {Error} - Throws if arrays is not defined + * @throws {TypeError} - Throws if any of the arguments is not an array + */ +export const getUniqueList = (...arrays) => { + if (arrays.length === 0) { + throw new Error('At least one array must be defined.'); + } + // Check if every argument is an array + arrays.forEach((array, index) => { + if (!Array.isArray(array)) { + throw new TypeError( + `Argument at position ${index} is not an array. Found ${typeof array}.`, + ); + } + }); + + // Flatten the arrays + const flattenedArray = arrays.flat(); + + // Create array with unique items + const uniqueArray = Array.from(new Set(flattenedArray)); + + return uniqueArray; +}; diff --git a/app/util/general/index.test.ts b/app/util/general/index.test.ts index 9ae670577ca..e662e147fc4 100644 --- a/app/util/general/index.test.ts +++ b/app/util/general/index.test.ts @@ -6,6 +6,7 @@ import { getURLProtocol, isIPFSUri, deepJSONParse, + getUniqueList, } from '.'; describe('capitalize', () => { @@ -190,3 +191,29 @@ describe('deepJSONParse function', () => { }); }); }); + +describe('getUniqueList function', () => { + it('should throw error if no arguments are passed in', async () => { + const expectedError = 'At least one array must be defined.'; + expect(() => getUniqueList()).toThrow(expectedError); + }); + it('should throw type error if an argument is not an array', async () => { + const testArray = ['0x1', '0x2', '0x3']; + const notAnArray = 'X' as unknown as string[]; + const expectedErrorMessage = `Argument at position 1 is not an array. Found ${typeof notAnArray}`; + expect(() => getUniqueList(testArray, notAnArray)).toThrow( + expectedErrorMessage, + ); + }); + it('should return an array with unique items', async () => { + const testArray = ['0x1', '0x2']; + const testArray2 = ['0x2', '0x3']; + const expectedArray = ['0x1', '0x2', '0x3']; + expect(getUniqueList(testArray, testArray2)).toEqual(expectedArray); + }); + it('should return the same array if all arrays from the arguments are the same', async () => { + const testArray = ['0x1', '0x2', '0x3']; + const sameTestArray = [...testArray]; + expect(getUniqueList(testArray, sameTestArray)).toEqual(testArray); + }); +}); diff --git a/app/util/networks/index.js b/app/util/networks/index.js index e69126857c6..3afbca9e244 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -111,6 +111,7 @@ export const BLOCKAID_SUPPORTED_CHAIN_IDS = [ NETWORKS_CHAIN_ID.AVAXCCHAIN, NETWORKS_CHAIN_ID.LINEA_MAINNET, NETWORKS_CHAIN_ID.SEPOLIA, + NETWORKS_CHAIN_ID.OPBNB, ]; export const BLOCKAID_SUPPORTED_NETWORK_NAMES = { @@ -122,6 +123,7 @@ export const BLOCKAID_SUPPORTED_NETWORK_NAMES = { [NETWORKS_CHAIN_ID.ARBITRUM]: 'Arbitrum', [NETWORKS_CHAIN_ID.LINEA_MAINNET]: 'Linea', [NETWORKS_CHAIN_ID.SEPOLIA]: 'Sepolia', + [NETWORKS_CHAIN_ID.OPBNB]: 'opBNB', }; export default NetworkList; diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js index 3d65f9d33b3..a4a6ad3d1a5 100644 --- a/app/util/test/testSetup.js +++ b/app/util/test/testSetup.js @@ -20,6 +20,11 @@ jest.mock('react-native', () => { return originalModule; }); +jest.mock('../../lib/snaps/preinstalled-snaps', () => + // eslint-disable-next-line no-console + console.log("do nothing since we aren't testing the pre installed snaps"), +); + jest.mock('react-native-fs', () => ({ CachesDirectoryPath: jest.fn(), DocumentDirectoryPath: jest.fn(), @@ -149,7 +154,7 @@ jest.mock('react-native-branch', () => ({ }, })); jest.mock('react-native-sensors', () => 'RNSensors'); -jest.mock('react-native-search-api', () => 'SearchApi'); +jest.mock('@metamask/react-native-search-api', () => 'SearchApi'); jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'), ); diff --git a/bitrise.yml b/bitrise.yml index a5b64b294df..f7d47cac41a 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -200,6 +200,8 @@ workflows: inputs: - content: |- #!/usr/bin/env bash + echo "Gems being installed with bundler gem" + bundle install echo "Node $NODE_VERSION being installed with asdf" asdf list all nodejs asdf install nodejs "$NODE_VERSION" diff --git a/docs/readme/environment.md b/docs/readme/environment.md index 824a6587076..e5e36abb22f 100644 --- a/docs/readme/environment.md +++ b/docs/readme/environment.md @@ -4,12 +4,11 @@ ### Package Manager -Install `brew` package manager. -_NOTE:_ To successfully run the iOS e2e tests, it is essential to install the brew package manager. +Install `brew` package manager. +_NOTE:_ To successfully run the iOS e2e tests, it is essential to install the brew package manager. [How to install brew](https://brew.sh/#install) - ### watchman Watchman is a tool by Facebook for watching changes in the filesystem. It is highly recommended you install it for better performance. @@ -22,16 +21,16 @@ brew install watchman MacOS ships with an old ruby version that is incompatible with this project -It is recommended to install a Ruby version manager such as [rbenv](https://github.com/rbenv/rbenv?tab=readme-ov-file#installation), [rvm](https://github.com/rvm/rvm?tab=readme-ov-file#installing-rvm), [asdf](https://asdf-vm.com/guide/getting-started.html#_3-install-asdf) +It is recommended to install a Ruby version manager such as [rbenv](https://github.com/rbenv/rbenv?tab=readme-ov-file#installation) Install ruby version defined in the file `.ruby-version` -### CocoaPods +### Gems + +Install [`bundler`](https://bundler.io/) gem to manage and install gems such as Cocoapods. The `bundle install` command, which is run during `yarn setup` handles installing gem versions as specified in the project's `GemFile` -With the correct version of ruby installed, CocoaPods can be installed sudo-less in your system using `gem` ```bash -gem install activesupport -v 7.0.8 && \ -gem install cocoapods -v 1.12.1 +gem install bundler -v 2.5.8 && bundle install ``` ### Xcode @@ -57,6 +56,7 @@ Install node version defined in the file `.nvmrc` ### Yarn v1 With the correct Node version installed, Yarn v1 can be installed sudo-less in your system using `npm` + ```bash npm install -g yarn ``` @@ -65,21 +65,19 @@ npm install -g yarn Install [Android Studio](https://developer.android.com/studio) -Set environment variable `JAVA_HOME=/Applications/Android Studio.app/Contents/jbr/Contents/Home` to use java version shipped on the Android Studio App - - - - Go to Settings > Languages & Frameworks > Android SDK - - Shortcut: Selecting `More Actions` > `SDK Manager` from the "Welcome to Android Studio" page will also bring you here. - - Select `SDK Tools` tab - - Check `Show Package Details` option below the tools list to show available versions - - Locate `NDK (Side-by-side)` option in the tools list - - Check NDK version `24.0.8215888` - - Locate `CMake` option in the tools list - - Check CMake version `3.22.1` - - Click "Apply" or "OK" to download - - Finally, start the emulator from Android Studio: - - Open "Virtual Device Manager" - - Launch emulator for "Pixel 5 " +- Set environment variable `JAVA_HOME=/Applications/Android Studio.app/Contents/jbr/Contents/Home` to use the Java version shipped with Android Studio +- Go to Settings > Languages & Frameworks > Android SDK +- Shortcut: Selecting `More Actions` > `SDK Manager` from the "Welcome to Android Studio" page will also bring you here. + - Select `SDK Tools` tab + - Check `Show Package Details` option below the tools list to show available versions + - Locate `NDK (Side-by-side)` option in the tools list + - Check NDK version `24.0.8215888` + - Locate `CMake` option in the tools list + - Check CMake version `3.22.1` + - Click "Apply" or "OK" to download +- Finally, start the emulator from Android Studio: + - Open "Virtual Device Manager" + - Launch emulator for "Pixel 5 " WIP diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index 4c2354bc85c..5810f2f815c 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -638,12 +638,7 @@ class FixtureBuilder { caveats: [ { type: 'restrictReturnedAccounts', - value: [ - { - address: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', - lastUsed: 1692603404804, - }, - ], + value: ['0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3'], }, ], date: 1664388714636, diff --git a/e2e/pages/Browser.js b/e2e/pages/Browser.js deleted file mode 100644 index 111d151a8b6..00000000000 --- a/e2e/pages/Browser.js +++ /dev/null @@ -1,228 +0,0 @@ -import TestHelpers from '../helpers'; -import { MULTI_TAB_ADD_BUTTON } from '../../wdio/screen-objects/testIDs/BrowserScreen/MultiTab.testIds'; -import { - BROWSER_SCREEN_ID, - HOME_BUTTON, - TABS_BUTTON, - OPTIONS_BUTTON, - SEARCH_BUTTON, - NAVBAR_TITLE_NETWORK, - ANDROID_BROWSER_WEBVIEW_ID, -} from '../../wdio/screen-objects/testIDs/BrowserScreen/BrowserScreen.testIds'; -import { URL_INPUT_BOX_ID } from '../../wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds'; -import { NOTIFICATION_TITLE } from '../../wdio/screen-objects/testIDs/Components/Notification.testIds'; -import { - testDappConnectButtonCooridinates, - testDappSendEIP1559ButtonCoordinates, -} from '../viewHelper'; -import { TEST_DAPP_LOCAL_URL } from './TestDApp'; -import { - BrowserViewSelectorsIDs, - BrowserViewSelectorsText, -} from '../selectors/BrowserView.selectors'; -import { CommonSelectorsText } from '../selectors/Common.selectors'; -import { AccountOverviewSelectorsIDs } from '../selectors/AccountOverview.selectors'; -import { AddBookmarkViewSelectorsIDs } from '../selectors/AddBookmarkView.selectors'; - -const TEST_DAPP = 'https://metamask.github.io/test-dapp/'; - -export default class Browser { - static async tapUrlInputBox() { - await TestHelpers.waitAndTap(NAVBAR_TITLE_NETWORK); - await TestHelpers.delay(1000); - } - - static async tapBottomSearchBar() { - await TestHelpers.waitAndTap(SEARCH_BUTTON); - } - - static async tapOptionsButton() { - await TestHelpers.waitAndTap(OPTIONS_BUTTON); - await TestHelpers.checkIfExists(OPTIONS_BUTTON); - } - - static async tapOpenAllTabsButton() { - await TestHelpers.checkIfExists(TABS_BUTTON); - await TestHelpers.waitAndTap(TABS_BUTTON); - } - - static async tapOpenNewTabButton() { - await TestHelpers.waitAndTap(MULTI_TAB_ADD_BUTTON); - } - static async tapNetworkAvatarButtonOnBrowser() { - await TestHelpers.waitAndTap(BrowserViewSelectorsIDs.AVATAR_IMAGE); - } - - static async tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp() { - if (device.getPlatform() === 'android') { - await TestHelpers.delay(3000); // to wait until toast notifcation disappears - await TestHelpers.tapByDescendentTestID( - AccountOverviewSelectorsIDs.ACCOUNT_BUTTON, - BrowserViewSelectorsIDs.AVATAR_IMAGE, - ); - } else { - await this.tapNetworkAvatarButtonOnBrowser(); - } - } - - static async tapAddToFavoritesButton() { - await TestHelpers.tapByText(BrowserViewSelectorsText.ADD_FAVORITES_BUTTON); - } - - static async tapAddBookmarksButton() { - if (device.getPlatform() === 'android') { - await TestHelpers.waitAndTapByLabel( - AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON, - ); - } else { - await TestHelpers.waitAndTap(AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON); - } - } - static async tapHomeButton() { - await TestHelpers.tap(HOME_BUTTON); - } - - static async tapBackToSafetyButton() { - await TestHelpers.tapByText(BrowserViewSelectorsText.BACK_TO_SAFETY_BUTTON); - } - static async tapReturnHomeButton() { - await TestHelpers.tapByText(BrowserViewSelectorsText.RETURN_HOME); - } - - static async navigateToURL(url) { - if (device.getPlatform() === 'ios') { - await TestHelpers.clearField(URL_INPUT_BOX_ID); - await TestHelpers.typeTextAndHideKeyboard(URL_INPUT_BOX_ID, url); - await TestHelpers.delay(2000); - } else { - await TestHelpers.clearField(URL_INPUT_BOX_ID); - await TestHelpers.replaceTextInField(URL_INPUT_BOX_ID, url); - await element(by.id(URL_INPUT_BOX_ID)).tapReturnKey(); - } - } - static async scrollToTakeTourOnBrowserPage() { - // Scroll on browser to show tutorial box and tap to skip - if (device.getPlatform() === 'ios') { - await TestHelpers.swipe(BROWSER_SCREEN_ID, 'up'); - } else { - await TestHelpers.checkIfExists(ANDROID_BROWSER_WEBVIEW_ID); - await TestHelpers.swipe(ANDROID_BROWSER_WEBVIEW_ID, 'up'); - await TestHelpers.delay(1000); - } - } - - static async goToTestDappAndTapConnectButton() { - await TestHelpers.delay(3000); - await this.tapUrlInputBox(); - await this.navigateToURL(TEST_DAPP); - await TestHelpers.delay(3000); - if (device.getPlatform() === 'ios') { - await TestHelpers.tapAtPoint( - BROWSER_SCREEN_ID, - testDappConnectButtonCooridinates, - ); - } else { - await TestHelpers.delay(3000); - await this.tapConnectButton(); - } - } - - /** @deprecated **/ - static async tapConnectButton() { - if (device.getPlatform() === 'android') { - await TestHelpers.tapWebviewElement( - BrowserViewSelectorsIDs.DAPP_CONNECT_BUTTON, - ); - } else { - await TestHelpers.tapAtPoint( - BROWSER_SCREEN_ID, - testDappConnectButtonCooridinates, - ); - } - } - - // The tapConnectButton() above has incorrect logic - "()" are missed - // Please change existing tests to use the following method - static async tapConnectButtonNew() { - if (device.getPlatform() === 'android') { - await TestHelpers.tapWebviewElement( - BrowserViewSelectorsIDs.DAPP_CONNECT_BUTTON, - ); - } else { - await TestHelpers.tapAtPoint( - BROWSER_SCREEN_ID, - testDappConnectButtonCooridinates, - ); - } - } - - static async tapSendEIP1559() { - // this method only works for android // at this moment in time only android supports interacting with webviews:https://wix.github.io/Detox/docs/api/webviews - - if (device.getPlatform() === 'android') { - await TestHelpers.swipe(BROWSER_SCREEN_ID, 'up', 'slow', 0.5); - await TestHelpers.delay(1500); - await TestHelpers.tapWebviewElement( - BrowserViewSelectorsIDs.DAPP_EIP1559_BUTTON, - ); - } else { - await TestHelpers.swipe(BROWSER_SCREEN_ID, 'up', 'slow', 0.1); // scrolling to the SendEIP1559 button - await TestHelpers.tapAtPoint( - BROWSER_SCREEN_ID, - testDappSendEIP1559ButtonCoordinates, - ); - } - await TestHelpers.tapByText(BrowserViewSelectorsText.CONFIRM_BUTTON, 1); - } - static async waitForBrowserPageToLoad() { - await TestHelpers.delay(5000); - } - - static async isVisible() { - await TestHelpers.checkIfVisible(BROWSER_SCREEN_ID); - } - - static async isNotVisible() { - await TestHelpers.checkIfNotVisible(BROWSER_SCREEN_ID); - } - - static async isAddBookmarkScreenVisible() { - await TestHelpers.checkIfVisible(AddBookmarkViewSelectorsIDs.CONTAINER); - } - - static async isAddBookmarkScreenNotVisible() { - await TestHelpers.checkIfNotVisible(AddBookmarkViewSelectorsIDs.CONTAINER); - } - - static async isBackToSafetyButtonVisible() { - await TestHelpers.checkIfElementWithTextIsVisible( - BrowserViewSelectorsText.BACK_TO_SAFETY_BUTTON, - ); - } - static async isAccountToastVisible(accountName) { - const connectedAccountMessage = `${accountName} ${CommonSelectorsText.TOAST_CONNECTED_ACCOUNTS}`; - await TestHelpers.checkIfElementHasString( - NOTIFICATION_TITLE, - connectedAccountMessage, - ); - } - - static async isRevokeAllAccountToastVisible() { - await TestHelpers.checkIfElementHasString( - NOTIFICATION_TITLE, - CommonSelectorsText.TOAST_REVOKE_ACCOUNTS, - ); - } - - static async navigateToTestDApp() { - await this.tapUrlInputBox(); - await this.navigateToURL(TEST_DAPP_LOCAL_URL); - } - - static async isURLBarTextTestDapp() { - await TestHelpers.checkIfElementWithTextIsVisible( - BrowserViewSelectorsText.METAMASK_TEST_DAPP_URL, - 0, - ); - } -} diff --git a/e2e/pages/Browser/AddBookmarkView.js b/e2e/pages/Browser/AddBookmarkView.js new file mode 100644 index 00000000000..c2b731a4e31 --- /dev/null +++ b/e2e/pages/Browser/AddBookmarkView.js @@ -0,0 +1,21 @@ +import { AddBookmarkViewSelectorsIDs } from '../../selectors/Browser/AddBookmarkView.selectors'; +import Gestures from '../../utils/Gestures'; +import Matchers from '../../utils/Matchers'; + +class AddFavoritesView { + get container() { + return Matchers.getElementByID(AddBookmarkViewSelectorsIDs.CONTAINER); + } + + get addBookmarkButton() { + return device.getPlatform() === 'ios' + ? Matchers.getElementByID(AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON) + : Matchers.getElementByLabel(AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON); + } + + async tapAddBookmarksButton() { + await Gestures.waitAndTap(this.addBookmarkButton); + } +} + +export default new AddFavoritesView(); diff --git a/e2e/pages/Browser/BrowserView.js b/e2e/pages/Browser/BrowserView.js new file mode 100644 index 00000000000..f7a8357bd1f --- /dev/null +++ b/e2e/pages/Browser/BrowserView.js @@ -0,0 +1,180 @@ +import TestHelpers from '../../helpers'; +import { TEST_DAPP_LOCAL_URL } from '../Browser/TestDApp'; + +import { + BrowserViewSelectorsIDs, + BrowserViewSelectorsText, + BrowserViewSelectorsXPaths, +} from '../../selectors/Browser/BrowserView.selectors'; +import { AccountOverviewSelectorsIDs } from '../../selectors/AccountOverview.selectors'; +import { BrowserURLBarSelectorsIDs } from '../../selectors/Browser/BrowserURLBar.selectors'; + +import { AddBookmarkViewSelectorsIDs } from '../../selectors/Browser/AddBookmarkView.selectors'; +import Gestures from '../../utils/Gestures'; +import Matchers from '../../utils/Matchers'; +class Browser { + get searchButton() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.SEARCH_BUTTON); + } + + get optionsButton() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.OPTIONS_BUTTON); + } + + get tabsButton() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.TABS_BUTTON); + } + + get homeButton() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.HOME_BUTTON); + } + + get browserScreenID() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.BROWSER_SCREEN_ID); + } + + get androidBrowserWebViewID() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID); + } + + get addressBar() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.URL_INPUT); + } + get urlInputBoxID() { + return Matchers.getElementByID(BrowserURLBarSelectorsIDs.URL_INPUT); + } + + get clearURLButton() { + return Matchers.getElementByID(BrowserViewSelectorsIDs.URL_CLEAR_ICON); + } + get backToSafetyButton() { + return Matchers.getElementByText( + BrowserViewSelectorsText.BACK_TO_SAFETY_BUTTON, + ); + } + + get returnHomeButton() { + return Matchers.getElementByText(BrowserViewSelectorsText.RETURN_HOME); + } + + get addFavourtiesButton() { + return Matchers.getElementByText( + BrowserViewSelectorsText.ADD_FAVORITES_BUTTON, + ); + } + get HomePageFavourtiesTab() { + return Matchers.getElementByXPath( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + BrowserViewSelectorsXPaths.FAVORITE_TAB, + ); + } + + get TestDappURLInFavourtiesTab() { + return device.getPlatform() === 'ios' + ? Matchers.getElementByXPath( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + BrowserViewSelectorsXPaths.TEST_DAPP_LINK, + ) + : Matchers.getElementByXPath( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + BrowserViewSelectorsXPaths.TEST_DAPP_TEXT, + ); + } + + get multiTabButton() { + return Matchers.getElementByID( + BrowserViewSelectorsIDs.MULTI_TAB_ADD_BUTTON, + ); + } + + get networkAvatarButton() { + return device.getPlatform() === 'ios' + ? Matchers.getElementByID(BrowserViewSelectorsIDs.AVATAR_IMAGE) + : Matchers.getElementByDescendant( + AccountOverviewSelectorsIDs.ACCOUNT_BUTTON, + BrowserViewSelectorsIDs.AVATAR_IMAGE, + ); + } + + get addBookmarkButton() { + return device.getPlatform() === 'ios' + ? Matchers.getElementByID(AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON) + : Matchers.getElementByLabel(AddBookmarkViewSelectorsIDs.CONFIRM_BUTTON); + } + + async getFavoritesURL(url) { + return Matchers.getElementByHref(url); + } + + async tapUrlInputBox() { + await Gestures.waitAndTap(this.addressBar); + } + + async tapBottomSearchBar() { + await Gestures.waitAndTap(this.searchButton); + } + + async tapOptionsButton() { + await Gestures.waitAndTap(this.optionsButton); + } + + async tapOpenAllTabsButton() { + await Gestures.waitAndTap(this.tabsButton); + } + + async tapOpenNewTabButton() { + await Gestures.waitAndTap(this.multiTabButton); + } + + async tapNetworkAvatarButtonOnBrowser() { + await Gestures.waitAndTap(this.networkAvatarButton); + } + + async tapAddToFavoritesButton() { + await Gestures.waitAndTap(this.addFavourtiesButton); + } + + async tapAddBookmarksButton() { + await Gestures.waitAndTap(this.addBookmarkButton); + } + + async tapHomeButton() { + await Gestures.waitAndTap(this.homeButton); + } + + async tapBackToSafetyButton() { + await Gestures.waitAndTap(this.backToSafetyButton); + } + + async tapReturnHomeButton() { + await Gestures.waitAndTap(this.returnHomeButton); + } + + async tapDappInFavorites() { + if (device.getPlatform() === 'ios') { + await Gestures.tapWebElement(await this.TestDappURLInFavourtiesTab); + } else { + await Gestures.tapWebElement(await this.HomePageFavourtiesTab); + await Gestures.tapWebElement(await this.TestDappURLInFavourtiesTab); + } + } + + async navigateToURL(url) { + await Gestures.waitAndTap(this.clearURLButton); + await device.disableSynchronization(); // because animations makes typing into the browser slow + + await Gestures.typeTextAndHideKeyboard(this.urlInputBoxID, url); + await device.enableSynchronization(); // re-enabling synchronization + } + + async waitForBrowserPageToLoad() { + await TestHelpers.delay(5000); + } + + async navigateToTestDApp() { + await this.tapUrlInputBox(); + await this.navigateToURL(TEST_DAPP_LOCAL_URL); + } +} + +export default new Browser(); diff --git a/e2e/pages/Browser/TestDApp.js b/e2e/pages/Browser/TestDApp.js new file mode 100644 index 00000000000..aee463497bd --- /dev/null +++ b/e2e/pages/Browser/TestDApp.js @@ -0,0 +1,135 @@ +import TestHelpers from '../../helpers'; +import { getLocalTestDappPort } from '../../fixtures/utils'; + +import { BrowserViewSelectorsIDs } from '../../selectors/Browser/BrowserView.selectors'; +import { TestDappSelectorsWebIDs } from '../../selectors/Browser/TestDapp.selectors'; + +import enContent from '../../../locales/languages/en.json'; + +import Browser from '../Browser/BrowserView'; +import Gestures from '../../utils/Gestures'; +import Matchers from '../../utils/Matchers'; + +export const TEST_DAPP_LOCAL_URL = `http://localhost:${getLocalTestDappPort()}`; +const CONFIRM_BUTTON_TEXT = enContent.confirmation_modal.confirm_cta; + +class TestDApp { + get androidContainer() { + return BrowserViewSelectorsIDs.ANDROID_CONTAINER; + } + + get confirmButtonText() { + return Matchers.getElementByText(CONFIRM_BUTTON_TEXT); + } + + get DappConnectButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.CONNECT_BUTTON, + ); + } + + get ApproveButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.APPROVE_TOKENS_BUTTON_ID, + ); + } + // This taps on the transfer tokens button under the "SEND TOKENS section" + get erc20TransferTokensButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.ERC_20_SEND_TOKENS_TRANSFER_TOKENS_BUTTON_ID, + ); + } + get ethSignButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.ETH_SIGN, + ); + } + get personalSignButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.PERSONAL_SIGN, + ); + } + get signTypedDataButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.SIGN_TYPE_DATA, + ); + } + get signTypedDataV3Button() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.SIGN_TYPE_DATA_V3, + ); + } + get signTypedDataV4Button() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.SIGN_TYPE_DATA_V4, + ); + } + // This taps on the transfer tokens button under the "SEND TOKENS section" + get nftTransferFromTokensButton() { + return Matchers.getElementByWebID( + BrowserViewSelectorsIDs.BROWSER_WEBVIEW_ID, + TestDappSelectorsWebIDs.NFT_TRANSFER_FROM_BUTTON_ID, + ); + } + + async connect() { + await this.tapButton(this.DappConnectButton); + } + + async tapApproveButton() { + await this.tapButton(this.ApproveButton); + } + + async tapEthSignButton() { + await this.tapButton(this.ethSignButton); + } + + async tapPersonalSignButton() { + await this.tapButton(this.personalSignButton); + } + + async tapTypedSignButton() { + await this.tapButton(this.signTypedDataButton); + } + + async tapTypedV3SignButton() { + await this.tapButton(this.signTypedDataV3Button); + } + + async tapTypedV4SignButton() { + await this.tapButton(this.signTypedDataV4Button); + } + async tapERC20TransferButton() { + await this.tapButton(this.erc20TransferTokensButton); + } + async tapNFTTransferButton() { + await this.tapButton(this.nftTransferFromTokensButton); + } + + async tapConfirmButton() { + await Gestures.tap(this.confirmButtonText, 0); + } + + async tapButton(elementId) { + await Gestures.scrollToWebViewPort(elementId); + await Gestures.tapWebElement(elementId); + } + + async navigateToTestDappWithContract({ contractAddress }) { + await Browser.tapUrlInputBox(); + await Browser.navigateToURL( + `${TEST_DAPP_LOCAL_URL}?scrollTo=''&contract=${contractAddress}`, + ); + await TestHelpers.delay(3000); // should have a better assertion that waits until the webpage loads + } +} + +export default new TestDApp(); diff --git a/e2e/pages/CommonView.js b/e2e/pages/CommonView.js index 32d4f10c61e..53a48ace4db 100644 --- a/e2e/pages/CommonView.js +++ b/e2e/pages/CommonView.js @@ -1,6 +1,9 @@ import Matchers from '../utils/Matchers'; import Gestures from '../utils/Gestures'; -import { CommonSelectorsIDs } from '../selectors/Common.selectors'; +import { + CommonSelectorsIDs, + CommonSelectorsText, +} from '../selectors/Common.selectors'; class CommonView { get okAlertByText() { @@ -18,6 +21,11 @@ class CommonView { get errorMessage() { return Matchers.getElementByID(CommonSelectorsIDs.ERROR_MESSAGE); } + + get disconnectedAccountsText() { + return Matchers.getElementByText(CommonSelectorsText.TOAST_REVOKE_ACCOUNTS); + } + async tapBackButton() { await Gestures.waitAndTap(this.backButton); } diff --git a/e2e/pages/Settings/NetworksView.js b/e2e/pages/Settings/NetworksView.js index 39c0f90d7d6..c73524d1b5a 100644 --- a/e2e/pages/Settings/NetworksView.js +++ b/e2e/pages/Settings/NetworksView.js @@ -175,7 +175,7 @@ class NetworkView { await Gestures.waitAndTap(this.rpcAddButton); } - async swipeToRPCTitleAndDismissKeyboard() { + async tapChainIDLabel() { // Because in bitrise the keyboard is blocking the "Add" CTA await Gestures.waitAndTap(this.chainIdLabel); } diff --git a/e2e/pages/TestDApp.js b/e2e/pages/TestDApp.js deleted file mode 100644 index 29e6611df20..00000000000 --- a/e2e/pages/TestDApp.js +++ /dev/null @@ -1,108 +0,0 @@ -import TestHelpers from '../helpers'; -import { testDappConnectButtonCooridinates } from '../viewHelper'; -import ConnectModal from './modals/ConnectModal'; -import Browser from './Browser'; -import enContent from '../../locales/languages/en.json'; -import { getLocalTestDappPort } from '../fixtures/utils'; -import { BrowserViewSelectorsIDs } from '../selectors/BrowserView.selectors'; -import Assertions from '../utils/Assertions'; - -export const TEST_DAPP_LOCAL_URL = `http://localhost:${getLocalTestDappPort()}`; - -const BUTTON_RELATIVE_PONT = { x: 200, y: 5 }; -const CONFIRM_BUTTON_TEXT = enContent.confirmation_modal.confirm_cta; - -export class TestDApp { - static async connect() { - await TestHelpers.tapAtPoint( - BrowserViewSelectorsIDs.ANDROID_CONTAINER, - testDappConnectButtonCooridinates, - ); - await Assertions.checkIfVisible(ConnectModal.container); - await ConnectModal.tapConnectButton(); - await TestHelpers.delay(3000); - } - - static async tapEthSignButton() { - await this.tapButton('ethSign'); - } - - static async tapPersonalSignButton() { - await this.tapButton('personalSign'); - } - - static async tapTypedSignButton() { - await this.tapButton('signTypedData'); - } - - static async tapTypedV3SignButton() { - await this.tapButton('signTypedDataV3'); - } - - static async tapTypedV4SignButton() { - await this.tapButton('signTypedDataV4'); - } - - static async tapConfirmButton() { - await TestHelpers.tapByText(CONFIRM_BUTTON_TEXT, 0); - } - - // All the below functions are temporary until Detox supports webview interaction in iOS. - - static async tapButton(elementId) { - await this.scrollToButton(elementId); - await TestHelpers.tapAtPoint( - BrowserViewSelectorsIDs.ANDROID_CONTAINER, - BUTTON_RELATIVE_PONT, - ); - await TestHelpers.delay(3000); - } - - static async scrollToButton(buttonId) { - await Browser.tapUrlInputBox(); - await Browser.navigateToURL( - `${TEST_DAPP_LOCAL_URL}?scrollTo=${buttonId}&time=${Date.now()}`, - ); - await TestHelpers.delay(3000); - } - - static async scrollToButtonWithParameter( - buttonId, - parameterName, - parameterValue, - ) { - await Browser.tapUrlInputBox(); - await Browser.navigateToURL( - `${TEST_DAPP_LOCAL_URL}?scrollTo=${buttonId}&${parameterName}=${parameterValue}`, - ); - await TestHelpers.delay(3000); - } - - static async navigateToTestDappWithContract(contractAddress) { - await Browser.tapUrlInputBox(); - await Browser.navigateToURL( - `${TEST_DAPP_LOCAL_URL}?contract=${contractAddress}`, - ); - } - - static async tapButtonWithContract({ buttonId, contractAddress }) { - await this.scrollToButtonWithParameter( - buttonId, - 'contract', - contractAddress, - ); - - if (device.getPlatform() === 'android') { - await TestHelpers.tapAtPoint( - BrowserViewSelectorsIDs.ANDROID_CONTAINER, - BUTTON_RELATIVE_PONT, - ); - } else { - await TestHelpers.delay(5000); - await TestHelpers.tapAtPoint( - BrowserViewSelectorsIDs.ANDROID_CONTAINER, - BUTTON_RELATIVE_PONT, - ); - } - } -} diff --git a/e2e/resources/externalsites.json b/e2e/resources/externalsites.json new file mode 100644 index 00000000000..c1c8dc1cf14 --- /dev/null +++ b/e2e/resources/externalsites.json @@ -0,0 +1,6 @@ +{ + "PHISHING_SITE": "http://www.empowr.com/FanFeed/Home.aspx", + "INVALID_URL": "https://quackquakc.easq", + "TEST_DAPP": "https://metamask.github.io/test-dapp/" + } + \ No newline at end of file diff --git a/e2e/selectors/ActivitiesView.selectors.js b/e2e/selectors/ActivitiesView.selectors.js index 516b38cb9f9..00e2c72304c 100644 --- a/e2e/selectors/ActivitiesView.selectors.js +++ b/e2e/selectors/ActivitiesView.selectors.js @@ -4,4 +4,7 @@ import enContent from '../../locales/languages/en.json'; export const ActivitiesViewSelectorsText = { TITLE: enContent.transactions_view.title, SWAP: enContent.swaps.transaction_label.swap, + SENT_COLLECTIBLE_MESSAGE_TEXT: enContent.transactions.sent_collectible, + SENT_TOKENS_MESSAGE_TEXT: enContent.transactions.sent_tokens, + CONFIRM_TEXT: enContent.transaction.confirmed, }; diff --git a/e2e/selectors/AddBookmarkView.selectors.js b/e2e/selectors/Browser/AddBookmarkView.selectors.js similarity index 100% rename from e2e/selectors/AddBookmarkView.selectors.js rename to e2e/selectors/Browser/AddBookmarkView.selectors.js diff --git a/e2e/selectors/Browser/AddFavorites.selectors.js b/e2e/selectors/Browser/AddFavorites.selectors.js new file mode 100644 index 00000000000..9ba08715199 --- /dev/null +++ b/e2e/selectors/Browser/AddFavorites.selectors.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line import/prefer-default-export +export const AddFavoritesViewSelectorsIDs = { + CANCEL_BUTTON: 'add-bookmark-cancel-button', + CONFIRM_BUTTON: 'add-bookmark-confirm-button', + CONTAINER: 'add-bookmark-screen', + BOOKMARK_TITLE: 'add-bookmark-title', + URL_TEXT: 'add-bookmark-url', +}; diff --git a/e2e/selectors/Browser/BrowserURLBar.selectors.js b/e2e/selectors/Browser/BrowserURLBar.selectors.js new file mode 100644 index 00000000000..e14929760b6 --- /dev/null +++ b/e2e/selectors/Browser/BrowserURLBar.selectors.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/prefer-default-export +export const BrowserURLBarSelectorsIDs = { + URL_INPUT: 'browser-modal-url-input', + CANCEL_BUTTON_ON_BROWSER_ID: 'cancel-url-button', + URL_CLEAR_ICON: 'url-clear-icon', +}; diff --git a/e2e/selectors/Browser/BrowserView.selectors.js b/e2e/selectors/Browser/BrowserView.selectors.js new file mode 100644 index 00000000000..10890a6d57f --- /dev/null +++ b/e2e/selectors/Browser/BrowserView.selectors.js @@ -0,0 +1,33 @@ +import enContent from '../../../locales/languages/en.json'; +import ExternalSites from '../../resources/externalsites.json'; +export const BrowserViewSelectorsIDs = { + BROWSER_WEBVIEW_ID: 'browser-webview', + AVATAR_IMAGE: 'network-avatar-image', + DAPP_EIP1559_BUTTON: 'sendEIP1559Button', + DAPP_CONNECT_BUTTON: 'connectButton', + URL_INPUT: 'url-input', + BROWSER_SCREEN_ID: 'browser-screen', + TABS_NUMBER: 'show-tabs-number', + BACK_BUTTON: 'back-arrow-button', + HOME_BUTTON: 'home-button', + FORWARD_BUTTON: 'go-forward-button', + SEARCH_BUTTON: 'search-button', + OPTIONS_BUTTON: 'options-button', + ACCOUNT_BUTTON: 'navbar-account-button', + TABS_BUTTON: 'show-tabs-button', + CANCEL_BUTTON_ON_BROWSER_ID: 'cancel-url-button', + URL_CLEAR_ICON: 'url-clear-icon', +}; + +export const BrowserViewSelectorsText = { + ADD_FAVORITES_BUTTON: enContent.browser.add_to_favorites, + BACK_TO_SAFETY_BUTTON: enContent.phishing.back_to_safety, + CONFIRM_BUTTON: enContent.confirmation_modal.confirm_cta, + RETURN_HOME: enContent.webview_error.return_home, + METAMASK_TEST_DAPP_URL: 'metamask.github.io', +}; +export const BrowserViewSelectorsXPaths = { + FAVORITE_TAB: `//div[@id='root']/div[@class='App']//ol//li[contains(text(), 'Favorites')]`, + TEST_DAPP_TEXT: `//div[@id='root']/div[@class='App']//p[contains(text(), 'metamask.github.io/test-dapp/')]`, + TEST_DAPP_LINK: `//a[contains(@href, '${ExternalSites.TEST_DAPP}')]`, +}; diff --git a/e2e/selectors/Browser/TestDapp.selectors.js b/e2e/selectors/Browser/TestDapp.selectors.js new file mode 100644 index 00000000000..a3452f7e40e --- /dev/null +++ b/e2e/selectors/Browser/TestDapp.selectors.js @@ -0,0 +1,12 @@ +// eslint-disable-next-line import/prefer-default-export +export const TestDappSelectorsWebIDs = { + CONNECT_BUTTON: 'connectButton', + ETH_SIGN: 'ethSign', + PERSONAL_SIGN: 'personalSign', + SIGN_TYPE_DATA: 'signTypedData', + SIGN_TYPE_DATA_V3: 'signTypedDataV3', + SIGN_TYPE_DATA_V4: 'signTypedDataV4', + APPROVE_TOKENS_BUTTON_ID: 'approveTokens', + ERC_20_SEND_TOKENS_TRANSFER_TOKENS_BUTTON_ID: 'transferTokens', + NFT_TRANSFER_FROM_BUTTON_ID: 'transferFromButton', +}; diff --git a/e2e/selectors/BrowserView.selectors.js b/e2e/selectors/BrowserView.selectors.js deleted file mode 100644 index a6b62c9d377..00000000000 --- a/e2e/selectors/BrowserView.selectors.js +++ /dev/null @@ -1,17 +0,0 @@ -import enContent from '../../locales/languages/en.json'; - -export const BrowserViewSelectorsIDs = { - ANDROID_CONTAINER: 'browser-webview', - AVATAR_IMAGE: 'network-avatar-image', - DAPP_EIP1559_BUTTON: 'sendEIP1559Button', - DAPP_CONNECT_BUTTON: 'connectButton', - URL_INPUT: 'browser-url-bar', -}; - -export const BrowserViewSelectorsText = { - ADD_FAVORITES_BUTTON: enContent.browser.add_to_favorites, - BACK_TO_SAFETY_BUTTON: enContent.phishing.back_to_safety, - CONFIRM_BUTTON: enContent.confirmation_modal.confirm_cta, - RETURN_HOME: enContent.webview_error.return_home, - METAMASK_TEST_DAPP_URL: 'metamask.github.io', -}; diff --git a/e2e/specs/browser/browser-tests.spec.js b/e2e/specs/browser/browser-tests.spec.js index 85f15e9db74..8ffdfd76f0c 100644 --- a/e2e/specs/browser/browser-tests.spec.js +++ b/e2e/specs/browser/browser-tests.spec.js @@ -1,9 +1,6 @@ 'use strict'; import TestHelpers from '../../helpers'; import { SmokeCore } from '../../tags'; -import Browser from '../../pages/Browser'; -import { BROWSER_SCREEN_ID } from '../../../wdio/screen-objects/testIDs/BrowserScreen/BrowserScreen.testIds'; -import TabBarComponent from '../../pages/TabBarComponent'; import { loginToApp } from '../../viewHelper'; import FixtureBuilder from '../../fixtures/fixture-builder'; import { @@ -11,12 +8,14 @@ import { startFixtureServer, stopFixtureServer, } from '../../fixtures/fixture-helper'; -import FixtureServer from '../../fixtures/fixture-server'; import { getFixturesServerPort } from '../../fixtures/utils'; +import FixtureServer from '../../fixtures/fixture-server'; +import Assertions from '../../utils/Assertions'; +import ExternalSites from '../../resources/externalsites.json'; +import Browser from '../../pages/Browser/BrowserView'; +import TabBarComponent from '../../pages/TabBarComponent'; +import AddBookmarkView from '../../pages/Browser/AddBookmarkView'; -const PHISHING_SITE = 'http://www.empowr.com/FanFeed/Home.aspx'; -const INVALID_URL = 'https://quackquakc.easq'; -const TEST_DAPP = 'https://metamask.github.io/test-dapp/'; const fixtureServer = new FixtureServer(); describe(SmokeCore('Browser Tests'), () => { @@ -42,72 +41,60 @@ describe(SmokeCore('Browser Tests'), () => { it('should navigate to browser', async () => { await TabBarComponent.tapBrowser(); // Check that we are on the browser screen - await Browser.isVisible(); + + await Assertions.checkIfVisible(await Browser.browserScreenID); }); it('should connect to the test dapp', async () => { await TestHelpers.delay(3000); // Tap on search in bottom navbar await Browser.tapUrlInputBox(); - await Browser.navigateToURL(TEST_DAPP); + await Browser.navigateToURL(ExternalSites.TEST_DAPP); await Browser.waitForBrowserPageToLoad(); }); it('should add the test dapp to favorites', async () => { // Check that we are still on the browser screen - await Browser.isVisible(); + // Tap on options await Browser.tapOptionsButton(); await Browser.tapAddToFavoritesButton(); - await Browser.isAddBookmarkScreenVisible(); - await Browser.tapAddBookmarksButton(); - await Browser.isAddBookmarkScreenNotVisible(); // Add bookmark screen should not be visible + + await Assertions.checkIfVisible(await AddBookmarkView.container); + + await AddBookmarkView.tapAddBookmarksButton(); + await Assertions.checkIfNotVisible(await AddBookmarkView.container); }); - it('tap on home button', async () => { - // Tap on home on bottom navbar + it('should tap on the test dapp in favorites on the home page', async () => { await Browser.tapHomeButton(); // Wait for page to load await TestHelpers.delay(1000); - await Browser.isVisible(); - }); - - it('should tap on the test dapp in favorites', async () => { - if (device.getPlatform() === 'ios') { - // Tapping on favourite iOS - await TestHelpers.tapAtPoint(BROWSER_SCREEN_ID, { x: 174, y: 281 }); - await Browser.waitForBrowserPageToLoad(); - } else { - // Tapping on favorite tap on Android - await TestHelpers.tapAtPoint(BROWSER_SCREEN_ID, { x: 274, y: 223 }); - await TestHelpers.tapAtPoint(BROWSER_SCREEN_ID, { x: 180, y: 275 }); - await Browser.waitForBrowserPageToLoad(); - } - await Browser.isURLBarTextTestDapp(); - await Browser.isVisible(); + await Browser.tapDappInFavorites(); + await Assertions.checkIfTextIsDisplayed('metamask.github.io'); + // } }); it('should test invalid URL', async () => { await TestHelpers.delay(2000); await Browser.tapBottomSearchBar(); // Clear text & Navigate to URL - await Browser.navigateToURL(INVALID_URL); + await Browser.navigateToURL(ExternalSites.INVALID_URL); await Browser.waitForBrowserPageToLoad(); await Browser.tapReturnHomeButton(); // Check that we are on the browser screen await TestHelpers.delay(1500); - await Browser.isVisible(); }); it('should test phishing sites', async () => { await Browser.tapBottomSearchBar(); // Clear text & Navigate to URL - await Browser.navigateToURL(PHISHING_SITE); + await Browser.navigateToURL(ExternalSites.PHISHING_SITE); await Browser.waitForBrowserPageToLoad(); - await Browser.isBackToSafetyButtonVisible(); + await Assertions.checkIfVisible(await Browser.backToSafetyButton); + await Browser.tapBackToSafetyButton(); // Check that we are on the browser screen await TestHelpers.delay(1500); - await Browser.isVisible(); }); }); diff --git a/e2e/specs/confirmations/approve-custom-erc20.spec.js b/e2e/specs/confirmations/approve-custom-erc20.spec.js index 68d907266bd..ec65fd3c22d 100644 --- a/e2e/specs/confirmations/approve-custom-erc20.spec.js +++ b/e2e/specs/confirmations/approve-custom-erc20.spec.js @@ -7,15 +7,15 @@ import { withFixtures, defaultGanacheOptions, } from '../../fixtures/fixture-helper'; + import TabBarComponent from '../../pages/TabBarComponent'; -import { TestDApp } from '../../pages/TestDApp'; +import TestDApp from '../../pages/Browser/TestDApp'; import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; -import enContent from '../../../locales/languages/en.json'; import ContractApprovalModal from '../../pages/modals/ContractApprovalModal'; import Assertions from '../../utils/Assertions'; +import { ActivitiesViewSelectorsText } from '../../selectors/ActivitiesView.selectors'; const HST_CONTRACT = SMART_CONTRACTS.HST; -const WEBVIEW_TEST_DAPP_APPROVE_TOKENS_BUTTON_ID = 'approveTokens'; describe(SmokeConfirmations('ERC20 tokens'), () => { beforeAll(async () => { @@ -45,11 +45,10 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { // Navigate to the browser screen await TabBarComponent.tapBrowser(); - // Approve ERC20 tokens - await TestDApp.tapButtonWithContract({ - buttonId: WEBVIEW_TEST_DAPP_APPROVE_TOKENS_BUTTON_ID, + await TestDApp.navigateToTestDappWithContract({ contractAddress: hstAddress, }); + await TestDApp.tapApproveButton(); //Input custom token amount await ContractApprovalModal.clearInput(); @@ -60,7 +59,6 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { ContractApprovalModal.approveTokenAmount, '2', ); - // Tap next button await ContractApprovalModal.tapNextButton(); @@ -69,10 +67,9 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { // Navigate to the activity screen await TabBarComponent.tapActivity(); - // Assert erc20 is approved - await TestHelpers.checkIfElementByTextIsVisible( - enContent.transaction.confirmed, + await Assertions.checkIfTextIsDisplayed( + ActivitiesViewSelectorsText.CONFIRM_TEXT, ); }, ); diff --git a/e2e/specs/confirmations/approve-default-erc20.spec.js b/e2e/specs/confirmations/approve-default-erc20.spec.js index 594d7aee6bd..11c0672f3e5 100644 --- a/e2e/specs/confirmations/approve-default-erc20.spec.js +++ b/e2e/specs/confirmations/approve-default-erc20.spec.js @@ -7,14 +7,17 @@ import { withFixtures, defaultGanacheOptions, } from '../../fixtures/fixture-helper'; -import TabBarComponent from '../../pages/TabBarComponent'; -import { TestDApp } from '../../pages/TestDApp'; + import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; -import enContent from '../../../locales/languages/en.json'; -import { ContractApprovalModalSelectorsIDs } from '../../selectors/Modals/ContractApprovalModal.selectors'; +import { ContractApprovalModalSelectorsText } from '../../selectors/Modals/ContractApprovalModal.selectors'; +import { ActivitiesViewSelectorsText } from '../../selectors/ActivitiesView.selectors'; + +import ContractApprovalModal from '../../pages/modals/ContractApprovalModal'; +import Assertions from '../../utils/Assertions'; +import TabBarComponent from '../../pages/TabBarComponent'; +import TestDApp from '../../pages/Browser/TestDApp'; const HST_CONTRACT = SMART_CONTRACTS.HST; -const WEBVIEW_TEST_DAPP_APPROVE_TOKENS_BUTTON_ID = 'approveTokens'; const EXPECTED_TOKEN_AMOUNT = '7'; describe(SmokeConfirmations('ERC20 tokens'), () => { @@ -44,42 +47,38 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { await loginToApp(); // Navigate to the browser screen await TabBarComponent.tapBrowser(); - - // Approve ERC20 tokens - await TestDApp.tapButtonWithContract({ - buttonId: WEBVIEW_TEST_DAPP_APPROVE_TOKENS_BUTTON_ID, + await TestDApp.navigateToTestDappWithContract({ contractAddress: hstAddress, }); + await TestDApp.tapApproveButton(); - // Assert the default token amount is shown - await TestHelpers.checkIfExists( - ContractApprovalModalSelectorsIDs.APPROVE_TOKEN_AMOUNT, + await Assertions.checkIfVisible( + ContractApprovalModal.approveTokenAmount, ); - await expect( - element( - by.id(ContractApprovalModalSelectorsIDs.APPROVE_TOKEN_AMOUNT), - ), - ).toHaveText(EXPECTED_TOKEN_AMOUNT); - + await Assertions.checkIfElementToHaveText( + ContractApprovalModal.approveTokenAmount, + EXPECTED_TOKEN_AMOUNT, + ); // Tap next button - await TestHelpers.checkIfElementWithTextIsVisible( - enContent.transaction.next, + await Assertions.checkIfTextIsDisplayed( + ContractApprovalModalSelectorsText.NEXT, ); - await TestHelpers.tapByText(enContent.transaction.next); + await ContractApprovalModal.tapNextButton(); - // Tap approve button - await TestHelpers.checkIfElementWithTextIsVisible( - enContent.transactions.tx_review_approve, + await Assertions.checkIfTextIsDisplayed( + ContractApprovalModalSelectorsText.APPROVE, ); - await TestHelpers.tapByText(enContent.transactions.tx_review_approve); + // Tap approve button + await ContractApprovalModal.tapApproveButton(); // Navigate to the activity screen await TabBarComponent.tapActivity(); // Assert erc20 is approved - await TestHelpers.checkIfElementByTextIsVisible( - enContent.transaction.confirmed, + + await Assertions.checkIfTextIsDisplayed( + ActivitiesViewSelectorsText.CONFIRM_TEXT, ); }, ); diff --git a/e2e/specs/confirmations/send-erc20-with-dapp.spec.js b/e2e/specs/confirmations/send-erc20-with-dapp.spec.js index df5d1922ed1..944e1e50301 100644 --- a/e2e/specs/confirmations/send-erc20-with-dapp.spec.js +++ b/e2e/specs/confirmations/send-erc20-with-dapp.spec.js @@ -7,14 +7,14 @@ import { withFixtures, defaultGanacheOptions, } from '../../fixtures/fixture-helper'; -import TabBarComponent from '../../pages/TabBarComponent'; -import { TestDApp } from '../../pages/TestDApp'; import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; -import enContent from '../../../locales/languages/en.json'; +import { ActivitiesViewSelectorsText } from '../../selectors/ActivitiesView.selectors'; + +import TabBarComponent from '../../pages/TabBarComponent'; +import TestDApp from '../../pages/Browser/TestDApp'; +import Assertions from '../../utils/Assertions'; const HST_CONTRACT = SMART_CONTRACTS.HST; -const SENT_TOKENS_MESSAGE_TEXT = enContent.transactions.sent_tokens; -const WEBVIEW_TEST_DAPP_TRANSFER_TOKENS_BUTTON_ID = 'transferTokens'; describe(SmokeConfirmations('ERC20 tokens'), () => { beforeAll(async () => { @@ -42,14 +42,15 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { // Navigate to the browser screen await TabBarComponent.tapBrowser(); - - // Transfer ERC20 tokens - await TestDApp.tapButtonWithContract({ - buttonId: WEBVIEW_TEST_DAPP_TRANSFER_TOKENS_BUTTON_ID, + await TestDApp.navigateToTestDappWithContract({ contractAddress: hstAddress, }); await TestHelpers.delay(3000); + // Transfer ERC20 tokens + await TestDApp.tapERC20TransferButton(); + await TestHelpers.delay(3000); + // Tap confirm button await TestDApp.tapConfirmButton(); @@ -57,8 +58,8 @@ describe(SmokeConfirmations('ERC20 tokens'), () => { await TabBarComponent.tapActivity(); // Assert "Sent Tokens" transaction is displayed - await TestHelpers.checkIfElementByTextIsVisible( - SENT_TOKENS_MESSAGE_TEXT, + await Assertions.checkIfTextIsDisplayed( + ActivitiesViewSelectorsText.SENT_TOKENS_MESSAGE_TEXT, ); }, ); diff --git a/e2e/specs/confirmations/send-erc721.spec.js b/e2e/specs/confirmations/send-erc721.spec.js index 494c9531a46..3cc75c0ade7 100644 --- a/e2e/specs/confirmations/send-erc721.spec.js +++ b/e2e/specs/confirmations/send-erc721.spec.js @@ -3,20 +3,20 @@ import { SmokeConfirmations } from '../../tags'; import TestHelpers from '../../helpers'; import { loginToApp } from '../../viewHelper'; + import TabBarComponent from '../../pages/TabBarComponent'; -import { TestDApp } from '../../pages/TestDApp'; +import TestDApp from '../../pages/Browser/TestDApp'; import FixtureBuilder from '../../fixtures/fixture-builder'; import { withFixtures, defaultGanacheOptions, } from '../../fixtures/fixture-helper'; -import enContent from '../../../locales/languages/en.json'; import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; +import { ActivitiesViewSelectorsText } from '../../selectors/ActivitiesView.selectors'; +import Assertions from '../../utils/Assertions'; describe(SmokeConfirmations('ERC721 tokens'), () => { const NFT_CONTRACT = SMART_CONTRACTS.NFTS; - const SENT_COLLECTIBLE_MESSAGE_TEXT = enContent.transactions.sent_collectible; - const WEBVIEW_TEST_DAPP_TRANSFER_FROM_BUTTON_ID = 'transferFromButton'; beforeAll(async () => { jest.setTimeout(150000); @@ -43,15 +43,12 @@ describe(SmokeConfirmations('ERC721 tokens'), () => { // Navigate to the browser screen await TabBarComponent.tapBrowser(); - - // Navigate to the ERC721 url - await TestDApp.navigateToTestDappWithContract(nftsAddress); - - // Transfer NFT - await TestDApp.tapButtonWithContract({ - buttonId: WEBVIEW_TEST_DAPP_TRANSFER_FROM_BUTTON_ID, + await TestDApp.navigateToTestDappWithContract({ contractAddress: nftsAddress, }); + // Transfer NFT + + await TestDApp.tapNFTTransferButton(); await TestHelpers.delay(3000); await TestDApp.tapConfirmButton(); @@ -60,8 +57,8 @@ describe(SmokeConfirmations('ERC721 tokens'), () => { await TabBarComponent.tapActivity(); // Assert collectible is sent - await TestHelpers.checkIfElementByTextIsVisible( - SENT_COLLECTIBLE_MESSAGE_TEXT, + await Assertions.checkIfTextIsDisplayed( + ActivitiesViewSelectorsText.SENT_COLLECTIBLE_MESSAGE_TEXT, ); }, ); diff --git a/e2e/specs/confirmations/signatures/eth-sign.spec.js b/e2e/specs/confirmations/signatures/eth-sign.spec.js index 346575d0fb8..aef725c156a 100644 --- a/e2e/specs/confirmations/signatures/eth-sign.spec.js +++ b/e2e/specs/confirmations/signatures/eth-sign.spec.js @@ -1,9 +1,9 @@ 'use strict'; -import Browser from '../../../pages/Browser'; +import Browser from '../../../pages/Browser/BrowserView'; import TabBarComponent from '../../../pages/TabBarComponent'; import { loginToApp } from '../../../viewHelper'; import SigningModal from '../../../pages/modals/SigningModal'; -import { TestDApp } from '../../../pages/TestDApp'; +import TestDApp from '../../../pages/Browser/TestDApp'; import FixtureBuilder from '../../../fixtures/fixture-builder'; import { withFixtures, @@ -12,8 +12,6 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; -const MAX_ATTEMPTS = 3; - describe(SmokeConfirmations('Eth Sign'), () => { beforeAll(async () => { jest.setTimeout(2500000); @@ -42,18 +40,17 @@ describe(SmokeConfirmations('Eth Sign'), () => { await TabBarComponent.tapBrowser(); await Browser.navigateToTestDApp(); - await TestHelpers.retry(MAX_ATTEMPTS, async () => { - await TestDApp.tapEthSignButton(); - await SigningModal.isEthRequestVisible(); - await SigningModal.tapCancelButton(); + await TestDApp.tapEthSignButton(); + + await SigningModal.isEthRequestVisible(); + await SigningModal.tapCancelButton(); + await SigningModal.isNotVisible(); - await SigningModal.isNotVisible(); + await TestDApp.tapEthSignButton(); - await TestDApp.tapEthSignButton(); - await SigningModal.isEthRequestVisible(); - await SigningModal.tapSignButton(); - await SigningModal.isNotVisible(); - }); + await SigningModal.isEthRequestVisible(); + await SigningModal.tapSignButton(); + await SigningModal.isNotVisible(); }, ); }); diff --git a/e2e/specs/confirmations/signatures/personal-sign.spec.js b/e2e/specs/confirmations/signatures/personal-sign.spec.js index e794a25cb2a..877361cae8b 100644 --- a/e2e/specs/confirmations/signatures/personal-sign.spec.js +++ b/e2e/specs/confirmations/signatures/personal-sign.spec.js @@ -1,9 +1,9 @@ 'use strict'; -import Browser from '../../../pages/Browser'; +import Browser from '../../../pages/Browser/BrowserView'; import TabBarComponent from '../../../pages/TabBarComponent'; import { loginToApp } from '../../../viewHelper'; import SigningModal from '../../../pages/modals/SigningModal'; -import { TestDApp } from '../../../pages/TestDApp'; +import TestDApp from '../../../pages/Browser/TestDApp'; import FixtureBuilder from '../../../fixtures/fixture-builder'; import { withFixtures, @@ -12,8 +12,6 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; -const MAX_ATTEMPTS = 3; - describe(SmokeConfirmations('Personal Sign'), () => { beforeAll(async () => { jest.setTimeout(2500000); @@ -37,18 +35,15 @@ describe(SmokeConfirmations('Personal Sign'), () => { await TabBarComponent.tapBrowser(); await Browser.navigateToTestDApp(); - await TestHelpers.retry(MAX_ATTEMPTS, async () => { - await TestDApp.tapPersonalSignButton(); - await SigningModal.isPersonalRequestVisible(); - await SigningModal.tapCancelButton(); - await SigningModal.isNotVisible(); - - await TestDApp.tapPersonalSignButton(); - await SigningModal.isPersonalRequestVisible(); - await SigningModal.tapSignButton(); + await TestDApp.tapPersonalSignButton(); + await SigningModal.isPersonalRequestVisible(); + await SigningModal.tapCancelButton(); + await SigningModal.isNotVisible(); - await SigningModal.isNotVisible(); - }); + await TestDApp.tapPersonalSignButton(); + await SigningModal.isPersonalRequestVisible(); + await SigningModal.tapSignButton(); + await SigningModal.isNotVisible(); }, ); }); diff --git a/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js b/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js index 3194d12208b..f8791af5cd8 100644 --- a/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js @@ -1,9 +1,9 @@ 'use strict'; -import Browser from '../../../pages/Browser'; +import Browser from '../../../pages/Browser/BrowserView'; import TabBarComponent from '../../../pages/TabBarComponent'; import { loginToApp } from '../../../viewHelper'; import SigningModal from '../../../pages/modals/SigningModal'; -import { TestDApp } from '../../../pages/TestDApp'; +import TestDApp from '../../../pages/Browser/TestDApp'; import FixtureBuilder from '../../../fixtures/fixture-builder'; import { withFixtures, @@ -12,8 +12,6 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; -const MAX_ATTEMPTS = 3; - describe(SmokeConfirmations('Typed Sign V3'), () => { beforeAll(async () => { jest.setTimeout(2500000); @@ -37,18 +35,14 @@ describe(SmokeConfirmations('Typed Sign V3'), () => { await TabBarComponent.tapBrowser(); await Browser.navigateToTestDApp(); - await TestHelpers.retry(MAX_ATTEMPTS, async () => { - await TestDApp.tapTypedV3SignButton(); - await SigningModal.isTypedRequestVisible(); - - await SigningModal.tapCancelButton(); - await SigningModal.isNotVisible(); + await TestDApp.tapTypedV3SignButton(); + await SigningModal.isTypedRequestVisible(); + await SigningModal.tapCancelButton(); + await SigningModal.isNotVisible(); + await TestDApp.tapTypedV3SignButton(); - await TestDApp.tapTypedV3SignButton(); - await SigningModal.isTypedRequestVisible(); - await SigningModal.tapSignButton(); - await SigningModal.isNotVisible(); - }); + await SigningModal.tapSignButton(); + await SigningModal.isNotVisible(); }, ); }); diff --git a/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js b/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js index a8c32a516e5..45e6370ff4a 100644 --- a/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js @@ -1,9 +1,9 @@ 'use strict'; -import Browser from '../../../pages/Browser'; +import Browser from '../../../pages/Browser/BrowserView'; import TabBarComponent from '../../../pages/TabBarComponent'; import { loginToApp } from '../../../viewHelper'; import SigningModal from '../../../pages/modals/SigningModal'; -import { TestDApp } from '../../../pages/TestDApp'; +import TestDApp from '../../../pages/Browser/TestDApp'; import FixtureBuilder from '../../../fixtures/fixture-builder'; import { withFixtures, @@ -12,8 +12,6 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; -const MAX_ATTEMPTS = 3; - describe(SmokeConfirmations('Typed Sign V4'), () => { beforeAll(async () => { jest.setTimeout(2500000); @@ -36,18 +34,14 @@ describe(SmokeConfirmations('Typed Sign V4'), () => { await TabBarComponent.tapBrowser(); await Browser.navigateToTestDApp(); + await TestDApp.tapTypedV4SignButton(); + await SigningModal.isTypedRequestVisible(); + await SigningModal.tapCancelButton(); + await SigningModal.isNotVisible(); + await TestDApp.tapTypedV4SignButton(); - await TestHelpers.retry(MAX_ATTEMPTS, async () => { - await TestDApp.tapTypedV4SignButton(); - await SigningModal.isTypedRequestVisible(); - await SigningModal.tapCancelButton(); - await SigningModal.isNotVisible(); - - await TestDApp.tapTypedV4SignButton(); - - await SigningModal.tapSignButton(); - await SigningModal.isNotVisible(); - }); + await SigningModal.tapSignButton(); + await SigningModal.isNotVisible(); }, ); }); diff --git a/e2e/specs/confirmations/signatures/typed-sign.spec.js b/e2e/specs/confirmations/signatures/typed-sign.spec.js index cc37b3df2b3..8b73ba36f54 100644 --- a/e2e/specs/confirmations/signatures/typed-sign.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign.spec.js @@ -1,9 +1,9 @@ 'use strict'; -import Browser from '../../../pages/Browser'; +import Browser from '../../../pages/Browser/BrowserView'; import TabBarComponent from '../../../pages/TabBarComponent'; import { loginToApp } from '../../../viewHelper'; import SigningModal from '../../../pages/modals/SigningModal'; -import { TestDApp } from '../../../pages/TestDApp'; +import TestDApp from '../../../pages/Browser/TestDApp'; import FixtureBuilder from '../../../fixtures/fixture-builder'; import { withFixtures, @@ -12,8 +12,6 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; -const MAX_ATTEMPTS = 3; - describe(SmokeConfirmations('Typed Sign'), () => { beforeAll(async () => { jest.setTimeout(2500000); @@ -37,19 +35,14 @@ describe(SmokeConfirmations('Typed Sign'), () => { await TabBarComponent.tapBrowser(); await Browser.navigateToTestDApp(); - await TestHelpers.retry(MAX_ATTEMPTS, async () => { - await TestDApp.tapTypedSignButton(); - await SigningModal.isTypedRequestVisible(); - await SigningModal.tapCancelButton(); - - await SigningModal.isNotVisible(); - - await TestDApp.tapTypedSignButton(); - await SigningModal.isTypedRequestVisible(); - await SigningModal.tapSignButton(); + await TestDApp.tapTypedSignButton(); + await SigningModal.isTypedRequestVisible(); + await SigningModal.tapCancelButton(); + await SigningModal.isNotVisible(); + await TestDApp.tapTypedSignButton(); - await SigningModal.isNotVisible(); - }); + await SigningModal.tapSignButton(); + await SigningModal.isNotVisible(); }, ); }); diff --git a/e2e/specs/networks/add-custom-rpc.spec.js b/e2e/specs/networks/add-custom-rpc.spec.js index 73bf73c7937..51f490d9b1e 100644 --- a/e2e/specs/networks/add-custom-rpc.spec.js +++ b/e2e/specs/networks/add-custom-rpc.spec.js @@ -64,11 +64,14 @@ describe(Regression('Custom RPC Tests'), () => { await NetworkView.typeInChainId( CustomNetworks.Gnosis.providerConfig.chainId, ); + await NetworkView.tapChainIDLabel(); // Focus outside of text input field + await NetworkView.typeInNetworkSymbol( `${CustomNetworks.Gnosis.providerConfig.ticker}\n`, ); if (device.getPlatform() === 'ios') { - await NetworkView.swipeToRPCTitleAndDismissKeyboard(); // Focus outside of text input field + await NetworkView.tapChainIDLabel(); // Focus outside of text input field + await NetworkView.tapChainIDLabel(); // Focus outside of text input field await NetworkView.tapRpcNetworkAddButton(); } await Assertions.checkIfVisible(NetworkApprovalModal.container); diff --git a/e2e/specs/permission-systems/permission-system-delete-wallet.spec.js b/e2e/specs/permission-systems/permission-system-delete-wallet.spec.js index b5dcd156b37..fd14a6f4805 100644 --- a/e2e/specs/permission-systems/permission-system-delete-wallet.spec.js +++ b/e2e/specs/permission-systems/permission-system-delete-wallet.spec.js @@ -5,7 +5,7 @@ import OnboardingView from '../../pages/Onboarding/OnboardingView'; import ProtectYourWalletView from '../../pages/Onboarding/ProtectYourWalletView'; import CreatePasswordView from '../../pages/Onboarding/CreatePasswordView'; import WalletView from '../../pages/WalletView'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import SettingsView from '../../pages/Settings/SettingsView'; import TabBarComponent from '../../pages/TabBarComponent'; import SkipAccountSecurityModal from '../../pages/modals/SkipAccountSecurityModal'; @@ -46,12 +46,12 @@ describe( //validate connection to test dapp await TabBarComponent.tapBrowser(); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); await Browser.navigateToTestDApp(); - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfVisible(ConnectedAccountsModal.title); await ConnectedAccountsModal.scrollToBottomOfModal(); - await Assertions.checkIfNotVisible(ConnectedAccountsModal.title); + await TestHelpers.delay(2000); //go to settings then security & privacy await TabBarComponent.tapSettings(); @@ -70,8 +70,8 @@ describe( await DeleteWalletModal.tapDeleteMyWalletButton(); await TestHelpers.delay(2000); await Assertions.checkIfVisible(OnboardingView.container); - await Assertions.checkIfVisible(CommonView.toast); - await Assertions.checkIfNotVisible(CommonView.toast); + await Assertions.checkIfVisible(await CommonView.toast); + await Assertions.checkIfNotVisible(await CommonView.toast); await OnboardingView.tapCreateWallet(); //Create new wallet @@ -94,7 +94,7 @@ describe( //should no longer be connected to the dapp await TabBarComponent.tapBrowser(); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfNotVisible(ConnectedAccountsModal.title); await Assertions.checkIfVisible(NetworkListModal.testNetToggle); diff --git a/e2e/specs/permission-systems/permission-system-revoke-single-account.spec.js b/e2e/specs/permission-systems/permission-system-revoke-single-account.spec.js index 9bac3d0b8aa..afc70094d8f 100644 --- a/e2e/specs/permission-systems/permission-system-revoke-single-account.spec.js +++ b/e2e/specs/permission-systems/permission-system-revoke-single-account.spec.js @@ -1,7 +1,7 @@ 'use strict'; import TestHelpers from '../../helpers'; import { SmokeCore } from '../../tags'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import TabBarComponent from '../../pages/TabBarComponent'; import NetworkListModal from '../../pages/modals/NetworkListModal'; import ConnectedAccountsModal from '../../pages/modals/ConnectedAccountsModal'; @@ -9,6 +9,7 @@ import FixtureBuilder from '../../fixtures/fixture-builder'; import { withFixtures } from '../../fixtures/fixture-helper'; import { loginToApp } from '../../viewHelper'; import Assertions from '../../utils/Assertions'; +import CommonView from '../../pages/CommonView'; describe(SmokeCore('Revoke Single Account after connecting to a dapp'), () => { beforeAll(async () => { @@ -28,14 +29,16 @@ describe(SmokeCore('Revoke Single Account after connecting to a dapp'), () => { async () => { await loginToApp(); await TabBarComponent.tapBrowser(); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); await Browser.navigateToTestDApp(); - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); + await ConnectedAccountsModal.tapPermissionsButton(); + await TestHelpers.delay(5500); // this is because the toast is delayed. + await ConnectedAccountsModal.tapDisconnectAllButton(); - await Browser.isAccountToastVisible('Account 1'); + await Assertions.checkIfNotVisible(await CommonView.toast); - await TestHelpers.delay(5500); await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfNotVisible(ConnectedAccountsModal.title); await Assertions.checkIfVisible(NetworkListModal.networkScroll); diff --git a/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js b/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js index e588fa1f7c2..7851f71f575 100644 --- a/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js +++ b/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js @@ -1,9 +1,12 @@ 'use strict'; import TestHelpers from '../../helpers'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import AccountListView from '../../pages/AccountListView'; import TabBarComponent from '../../pages/TabBarComponent'; import ConnectedAccountsModal from '../../pages/modals/ConnectedAccountsModal'; + +import CommonView from '../../pages/CommonView'; + import { loginToApp } from '../../viewHelper'; import NetworkListModal from '../../pages/modals/NetworkListModal'; import FixtureBuilder from '../../fixtures/fixture-builder'; @@ -29,16 +32,17 @@ describe('Connecting to multiple dapps and revoking permission on one but stayin //should navigate to browser await loginToApp(); await TabBarComponent.tapBrowser(); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); //TODO: should re add connecting to an external swap step after detox has been updated await Browser.navigateToTestDApp(); - await Browser.isAccountToastVisible('Account 1'); - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfVisible(ConnectedAccountsModal.title); + // await TestHelpers.delay(1000); + + await Assertions.checkIfNotVisible(CommonView.toast); await ConnectedAccountsModal.tapConnectMoreAccountsButton(); - await TestHelpers.delay(1000); await AccountListView.tapAddAccountButton(); await AccountListView.tapCreateAccountButton(); await AccountListView.isAccount2VisibleAtIndex(0); @@ -46,11 +50,12 @@ describe('Connecting to multiple dapps and revoking permission on one but stayin await AccountListView.connectAccountsButton(); // should revoke accounts - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); await ConnectedAccountsModal.tapPermissionsButton(); await TestHelpers.delay(1500); await ConnectedAccountsModal.tapDisconnectAllButton(); - await Browser.isRevokeAllAccountToastVisible(); + await Assertions.checkIfNotVisible(await CommonView.toast); + await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfNotVisible(ConnectedAccountsModal.title); await Assertions.checkIfVisible(NetworkListModal.networkScroll); diff --git a/e2e/specs/quarantine/deeplinks.failing.js b/e2e/specs/quarantine/deeplinks.failing.js index 29530a3caa2..096ade01820 100644 --- a/e2e/specs/quarantine/deeplinks.failing.js +++ b/e2e/specs/quarantine/deeplinks.failing.js @@ -6,7 +6,7 @@ import ConnectModal from '../../pages/modals/ConnectModal'; import NetworkApprovalModal from '../../pages/modals/NetworkApprovalModal'; import NetworkAddedModal from '../../pages/modals/NetworkAddedModal'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import NetworkView from '../../pages/Settings/NetworksView'; import SettingsView from '../../pages/Settings/SettingsView'; import LoginView from '../../pages/LoginView'; @@ -181,7 +181,7 @@ describe(Regression('Deep linking Tests'), () => { await TestHelpers.checkIfElementWithTextIsVisible('app.sushi.com', 0); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); await Assertions.checkIfNotVisible(ConnectModal.container); }); }); diff --git a/e2e/specs/quarantine/permission-system-removing-imported-account.failing.js b/e2e/specs/quarantine/permission-system-removing-imported-account.failing.js index 02899044ba7..408c48ff428 100644 --- a/e2e/specs/quarantine/permission-system-removing-imported-account.failing.js +++ b/e2e/specs/quarantine/permission-system-removing-imported-account.failing.js @@ -5,7 +5,7 @@ import WalletView from '../../pages/WalletView'; import ImportAccountView from '../../pages/ImportAccountView'; import TabBarComponent from '../../pages/TabBarComponent'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import AccountListView from '../../pages/AccountListView'; import ConnectModal from '../../pages/modals/ConnectModal'; @@ -14,6 +14,7 @@ import NetworkListModal from '../../pages/modals/NetworkListModal'; import NetworkEducationModal from '../../pages/modals/NetworkEducationModal'; import Accounts from '../../../wdio/helpers/Accounts'; +import { TestDApp } from '../../pages/Browser/TestDApp'; import { importWalletWithRecoveryPhrase } from '../../viewHelper'; import AddAccountModal from '../../pages/modals/AddAccountModal'; @@ -38,12 +39,12 @@ describe( it('should navigate to browser', async () => { await TestHelpers.delay(2000); await TabBarComponent.tapBrowser(); - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); }); it('should trigger connect modal in the test dapp', async () => { await TestHelpers.delay(3000); - await Browser.goToTestDappAndTapConnectButton(); + await TestDApp.goToTestDappAndTapConnectButton(); }); it('should go to multiconnect in the connect account modal', async () => { @@ -67,7 +68,7 @@ describe( }); it('should switch to Sepolia', async () => { - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); await ConnectedAccountsModal.tapNetworksPicker(); await Assertions.checkIfVisible(NetworkListModal.networkScroll); await NetworkListModal.tapTestNetworkSwitch(); @@ -104,11 +105,11 @@ describe( await TestHelpers.delay(4500); await TabBarComponent.tapBrowser(); // Check that we are on the browser screen - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); }); it('imported account is not visible', async () => { - await Browser.tapNetworkAvatarButtonOnBrowserWhileAccountIsConnectedToDapp(); + await Browser.tapNetworkAvatarButtonOnBrowser(); await Assertions.checkIfNotVisible(ConnectedAccountsModal.title); await AccountListView.accountNameNotVisible('Account 2'); }); diff --git a/e2e/specs/settings/delete-wallet.spec.js b/e2e/specs/settings/delete-wallet.spec.js index b710b846db7..fa11374fb5d 100644 --- a/e2e/specs/settings/delete-wallet.spec.js +++ b/e2e/specs/settings/delete-wallet.spec.js @@ -50,9 +50,7 @@ describe( await ChangePasswordView.reEnterPassword(NEW_PASSWORD); // should lock wallet from Settings - await device.disableSynchronization(); // because the SRP tutorial video prevents the test from moving forward await CommonView.tapBackButton(); - await device.enableSynchronization(); await SettingsView.tapLock(); await SettingsView.tapYesAlertButton(); await LoginView.isVisible(); diff --git a/e2e/specs/wallet/start-exploring.spec.js b/e2e/specs/wallet/start-exploring.spec.js index e5a6c526e9b..5230135a9f4 100644 --- a/e2e/specs/wallet/start-exploring.spec.js +++ b/e2e/specs/wallet/start-exploring.spec.js @@ -9,7 +9,7 @@ import MetaMetricsOptIn from '../../pages/Onboarding/MetaMetricsOptInView'; import WalletView from '../../pages/WalletView'; import OnboardingSuccessView from '../../pages/Onboarding/OnboardingSuccessView'; import EnableAutomaticSecurityChecksView from '../../pages/EnableAutomaticSecurityChecksView'; -import Browser from '../../pages/Browser'; +import Browser from '../../pages/Browser/BrowserView'; import SkipAccountSecurityModal from '../../pages/modals/SkipAccountSecurityModal'; import OnboardingWizardModal from '../../pages/modals/OnboardingWizardModal'; import WhatsNewModal from '../../pages/modals/WhatsNewModal'; @@ -123,6 +123,6 @@ describe(SmokeCore('Start Exploring'), () => { } catch { // } - await Browser.isVisible(); + await Assertions.checkIfVisible(Browser.browserScreenID); }); }); diff --git a/e2e/utils/Assertions.js b/e2e/utils/Assertions.js index 5a4a904fde6..d304cd491c4 100644 --- a/e2e/utils/Assertions.js +++ b/e2e/utils/Assertions.js @@ -49,7 +49,7 @@ class Assertions { } /** - * Check if text is not visible. + * Check if text is visible. * @param {string} text - The text to check if displayed. * @param {number} [timeout=TIMEOUT] - Timeout in milliseconds. */ diff --git a/e2e/utils/Gestures.js b/e2e/utils/Gestures.js index 56af5878b89..34f0ef580fb 100644 --- a/e2e/utils/Gestures.js +++ b/e2e/utils/Gestures.js @@ -79,9 +79,12 @@ class Gestures { * Clear the text field of an element identified by ID. * * @param {Promise} elementID - ID of the element to clear - */ - static async clearField(elementID) { + * @param {number} timeout - Timeout for waiting (default: 8000ms) + + */ + static async clearField(elementID, timeout = 2500) { const element = await elementID; + await waitFor(element).toBeVisible().withTimeout(timeout); await element.replaceText(''); } @@ -94,9 +97,8 @@ class Gestures { */ static async typeTextAndHideKeyboard(elementID, text) { const element = await elementID; - if (device.getPlatform() === 'android') { - await this.clearField(element); - } + await this.clearField(element); + await element.typeText(text + '\n'); } @@ -155,6 +157,15 @@ class Gestures { .swipe(direction, speed, percentage, xStart, yStart); } + /** + * Scrolls the web element until its top is at the top of the viewport. + * @param {Promise} elementID - A promise resolving to the target element. + */ + static async scrollToWebViewPort(elem) { + const element = await elem; + await element.scrollToView(); + } + /** * Dynamically Scrolls to an element identified by ID. * diff --git a/e2e/utils/Matchers.js b/e2e/utils/Matchers.js index dcc09c20119..8f9c1afc6aa 100644 --- a/e2e/utils/Matchers.js +++ b/e2e/utils/Matchers.js @@ -76,31 +76,46 @@ class Matchers { /** * Get element by web ID. * - * @param {string} webID - The web ID of the element to locate + * * @param {string} webviewID - The web ID of the inner element to locate within the webview + * @param {string} innerID - The web ID of the browser webview * @return {Promise} Resolves to the located element */ - static async getElementByWebID(webID) { - return web.element(by.web.id(webID)); + static async getElementByWebID(webviewID, innerID) { + const myWebView = web(by.id(webviewID)); + return myWebView.element(by.web.id(innerID)); } /** * Get element by CSS selector. - * + * @param {string} webviewID - The web ID of the browser webview * @param {string} selector - CSS selector to locate the element * @return {Promise} - Resolves to the located element */ - static async getElementByCSSSelector(selector) { - return web.element(by.web.cssSelector(selector)); + + static async getElementByCSS(webviewID, selector) { + const myWebView = web(by.id(webviewID)); + return myWebView.element(by.web.cssSelector(selector)).atIndex(0); } /** * Get element by XPath. - * + * @param {string} webviewID - The web ID of the browser webview + * @param {string} xpath - XPath expression to locate the element + * @return {Promise} - Resolves to the located element + */ + static async getElementByXPath(webviewID, xpath) { + const myWebView = web(by.id(webviewID)); + return myWebView.element(by.web.xpath(xpath)).atIndex(0); + } + /** + * Get element by href. + * @param {string} webviewID - The web ID of the browser webview * @param {string} xpath - XPath expression to locate the element * @return {Promise} - Resolves to the located element */ - static async getElementByXPath(xpath) { - return web.element(by.web.xpath(xpath)); + static async getElementByHref(webviewID, url) { + const myWebView = web(by.id(webviewID)); + return myWebView.element(by.web.href(url)).atIndex(0); } /** diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 0119e6b33b0..6bc7c946a96 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1227,7 +1227,7 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", @@ -1290,7 +1290,7 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", @@ -1355,7 +1355,7 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", @@ -1414,7 +1414,7 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", @@ -1484,7 +1484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ( @@ -1531,7 +1531,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ( "$(inherited)", @@ -1574,13 +1574,13 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", ); INFOPLIST_FILE = "MetaMask/MetaMask-QA-info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1640,13 +1640,13 @@ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager", "$(SRCROOT)/../node_modules/react-native-share/ios", "$(SRCROOT)/../node_modules/react-native-branch/ios/**", - "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi", + "$(SRCROOT)/../node_modules/@metamask/react-native-search-api/ios/RCTSearchApi", "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**", "$(SRCROOT)/../node_modules/react-native-view-shot/ios", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", ); INFOPLIST_FILE = "MetaMask/MetaMask-QA-info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/Podfile b/ios/Podfile index 3d0274215ab..c292007f659 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -3,6 +3,8 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ platform :ios, '12.4' #min_ios_version_supported prepare_react_native_project! +# Ensures that versions from Gemfile is respected +ensure_bundler! linkage = ENV['USE_FRAMEWORKS'] if linkage != nil diff --git a/ios/Podfile.lock b/ios/Podfile.lock old mode 100644 new mode 100755 index 5b9ca46ccfd..3a260c8063e --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -723,7 +723,7 @@ DEPENDENCIES: - Permission-BluetoothPeripheral (from `../node_modules/react-native-permissions/ios/BluetoothPeripheral`) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - - RCTSearchApi (from `../node_modules/react-native-search-api`) + - "RCTSearchApi (from `../node_modules/@metamask/react-native-search-api`)" - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) @@ -866,7 +866,7 @@ EXTERNAL SOURCES: RCTRequired: :path: "../node_modules/react-native/Libraries/RCTRequired" RCTSearchApi: - :path: "../node_modules/react-native-search-api" + :path: "../node_modules/@metamask/react-native-search-api" RCTTypeSafety: :path: "../node_modules/react-native/Libraries/TypeSafety" React: @@ -1050,7 +1050,7 @@ SPEC CHECKSUMS: Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541 FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: ef91e487929d3be2675f9d8754d7d30840027acd GoogleAppMeasurement: f3abf08495ef2cba7829f15318c373b8d9226491 GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 GZIP: e6922ed5bdd1d77d84589d50821ac34ea0c38d4b @@ -1139,7 +1139,7 @@ SPEC CHECKSUMS: RNReanimated: b1220a0e5168745283ff5d53bfc7d2144b2cee1b RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb RNSensors: c363d486c879e181905dea84a2535e49af1c2d25 - RNSentry: 1b70955af02f86efc3c9a09989f0d0695b0d237d + RNSentry: 98e170b6eedc5c54e3ac88b6140fffb8b496deb4 RNShare: f116bbb04f310c665ca483d0bd1e88cf59b3b334 RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f RNVectorIcons: 6607bd3a30291d0edb56f9bbe7ae411ee2b928b0 @@ -1152,6 +1152,6 @@ SPEC CHECKSUMS: Yoga: 68c9c592c3e80ec37ff28db20eedb13d84aae5df YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: b041bcd5cf0f3b6d41c16392ee13a01dd5e6052a +PODFILE CHECKSUM: 9fbad5d11f3fd3d334c7bd651b28783ab9300999 -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/package.json b/package.json index c48bd2d14c3..2abe9f289d2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "watch": "./scripts/build.sh watcher watch", "watch:clean": "./scripts/build.sh watcher clean", "clean:ios": "rm -rf ios/build", - "pod:install": "command -v pod && (cd ios/ && pod install && cd ..) || echo \"Skipping pod install\"", + "pod:install": "command -v pod && (cd ios/ && bundle exec pod install && cd ..) || echo \"Skipping pod install\"", "clean:ppom": "rm -rf ppom/dist ppom/node_modules app/lib/ppom/ppom.html.js", "clean:android": "rm -rf android/app/build", "clean:node": "rm -rf node_modules && yarn --frozen-lockfile", @@ -150,23 +150,25 @@ "@metamask/gas-fee-controller": "^10.0.0", "@metamask/key-tree": "^9.0.0", "@metamask/keyring-api": "^4.0.0", + "@metamask/logging-controller": "^2.0.0", "@metamask/keyring-controller": "^13.0.0", - "@metamask/logging-controller": "^1.0.1", + "@metamask/message-signing-snap": "^0.3.3", "@metamask/network-controller": "^15.0.0", "@metamask/permission-controller": "7.1.0", "@metamask/phishing-controller": "^8.0.0", "@metamask/post-message-stream": "8.0.0", - "@metamask/ppom-validator": "0.29.0", + "@metamask/ppom-validator": "0.30.0", "@metamask/preferences-controller": "^8.0.0", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-animated-fox": "^2.1.0", "@metamask/react-native-button": "^3.0.0", "@metamask/react-native-payments": "^2.0.0", + "@metamask/react-native-search-api": "1.0.1", "@metamask/react-native-splash-screen": "^3.2.0", "@metamask/rpc-errors": "^6.2.1", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "^0.18.1", - "@metamask/signature-controller": "6.0.0", + "@metamask/signature-controller": "6.1.3", "@metamask/slip44": "3.1.0", "@metamask/snaps-controllers": "^5.0.0", "@metamask/snaps-rpc-methods": "^6.0.0", @@ -301,7 +303,6 @@ "react-native-safe-area-context": "^3.1.9", "react-native-screens": "3.19.0", "react-native-scrollable-tab-view": "^1.0.0", - "react-native-search-api": "ombori/react-native-search-api#8/head", "react-native-sensors": "5.3.0", "react-native-share": "7.3.7", "react-native-size-matters": "0.4.0", @@ -416,7 +417,7 @@ "browserstack-local": "^1.5.1", "chromedriver": "^123.0.1", "depcheck": "^1.4.7", - "detox": "^20.14.3", + "detox": "^20.20.3", "dotenv": "^16.0.3", "enzyme": "3.9.0", "enzyme-adapter-react-16": "1.10.0", diff --git a/scripts/setup.mjs b/scripts/setup.mjs index 818561fc201..79589343caf 100644 --- a/scripts/setup.mjs +++ b/scripts/setup.mjs @@ -2,6 +2,10 @@ import fs from 'fs'; import { $ } from 'execa'; import { Listr } from 'listr2'; +const IS_OSX = process.platform === 'darwin'; +// iOS builds are enabled by default on macOS only but can be enabled explicitly +const BUILD_IOS = process.argv.slice(2)?.[0] === '--build-ios' || IS_OSX; + const rendererOptions = { collapseErrors: false, showSkipMessage: false, @@ -9,7 +13,6 @@ const rendererOptions = { collapseSubtasks: false }; - /* * FIXME: We shouldn't be making system wide installs without user consent. * Should make it optional on non-CI environments @@ -26,8 +29,7 @@ const detoxGlobalInstallTask = { { title: 'Install applesimutils globally', task: async (_, appSimTask) => { - const isOSX = process.platform === 'darwin'; - if (!isOSX) { + if (!IS_OSX) { appSimTask.skip('Not macOS.'); } else { await $`brew tap wix/brew`; @@ -107,22 +109,35 @@ const ppomBuildTask = { } } +const gemInstallTask = { + title: 'Install gems', + task: (_, task) => task.newListr([ + { + title: 'Install gems using bundler', + task: async (_, gemInstallTask) => { + if (!BUILD_IOS) { + return gemInstallTask.skip('Skipping iOS.') + } + await $`bundle install`; + }, + }, + ], { + exitOnError: true, + concurrent: false, + rendererOptions + }) +} + const mainSetupTask = { title: 'Dependencies setup', task: (_, task) => task.newListr([ { title: 'Install iOS Pods', task: async (_, podInstallTask) => { - const isOSX = process.platform === 'darwin'; - if (!isOSX) { - podInstallTask.skip('Not macOS.'); - } else { - try { - await $`pod install --project-directory=ios`; - } catch (error) { - throw new Error(error); - } + if (!BUILD_IOS) { + return podInstallTask.skip('Skipping iOS.') } + await $`bundle exec pod install --project-directory=ios`; }, }, { @@ -189,6 +204,7 @@ const patchModulesTask = { }) } const tasks = new Listr([ + gemInstallTask, mainSetupTask, ppomBuildTask, patchModulesTask diff --git a/wdio/screen-objects/BrowserObject/AddressBarScreen.js b/wdio/screen-objects/BrowserObject/AddressBarScreen.js index 8eaf0309858..b350f41b3e4 100644 --- a/wdio/screen-objects/BrowserObject/AddressBarScreen.js +++ b/wdio/screen-objects/BrowserObject/AddressBarScreen.js @@ -11,7 +11,7 @@ import { class AddressBarScreen { get urlCancelButton() { - return Selectors.getElementByPlatform(CANCEL_BUTTON_ON_BROWSER_ID); + return Selectors.getXpathElementByResourceId(CANCEL_BUTTON_ON_BROWSER_ID); } get urlModalInput() { @@ -27,7 +27,7 @@ class AddressBarScreen { } get urlClearIcon() { - return Selectors.getElementByPlatform(URL_CLEAR_ICON); + return Selectors.getXpathElementByResourceId(URL_CLEAR_ICON); } async isAddressInputViewDisplayed() { diff --git a/yarn.lock b/yarn.lock index d09744901a8..9d9e999aa6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2343,6 +2343,11 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@flatten-js/interval-tree@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@flatten-js/interval-tree/-/interval-tree-1.1.3.tgz#7d9b4bb92042c6bbcefae5bbb822b5ec3c073e88" + integrity sha512-xhFWUBoHJFF77cJO1D6REjdgJEMRf2Y2Z+eKEPav8evGKcLSnj1ud5pLXQSbGuxF3VSvT1rWhMfVpXEKJLTL+A== + "@floating-ui/core@^1.4.2": version "1.5.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" @@ -3607,7 +3612,7 @@ "@metamask/controller-utils" "^6.0.0" "@metamask/utils" "^8.2.0" -"@metamask/approval-controller@^3.5.1", "@metamask/approval-controller@^3.5.2": +"@metamask/approval-controller@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-3.5.2.tgz#c8ca4578f053d719bff82b0c9fa1939d21978d46" integrity sha512-5jYMtPexeeNL8m+5Mzhayr1l367MbbBs0Wf+FBeL/xLv6nSNqvvNs/CXfvkqxLDZJKIgBCBPKg7eIu1U6MJ6ag== @@ -3618,7 +3623,7 @@ immer "^9.0.6" nanoid "^3.1.31" -"@metamask/approval-controller@^4.1.0": +"@metamask/approval-controller@^4.0.1", "@metamask/approval-controller@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-4.1.0.tgz#99bf137597d0392c5eebb784c0d43f2f40584e9d" integrity sha512-UdSf8787NMADvmGqC88y8GbzwljdWw4cDCs4NWfc1HIhG4mvvaAFZwjZpPvU59DuFG6WycNfNLyJ7lcJsiYCYg== @@ -3668,7 +3673,7 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/base-controller@^3.0.0", "@metamask/base-controller@^3.2.1", "@metamask/base-controller@^3.2.2", "@metamask/base-controller@^3.2.3": +"@metamask/base-controller@^3.0.0", "@metamask/base-controller@^3.2.2", "@metamask/base-controller@^3.2.3": version "3.2.3" resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-3.2.3.tgz#7436a14f6789acf0814952dabaa70ee4fb7d473c" integrity sha512-k66oZe7BOEx0D5N5X8feE/32QlrUTmiEHHAZU/yCac2+VHllJOCEQV/cTeaAtgepnEf8O7SskvYZN+eIjgS99w== @@ -3711,20 +3716,6 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.2.0.tgz#277764d0d56e37180ae7644a9d11eb96295b36fc" integrity sha512-SM6A4C7vXNbVpgMTX67kfW8QWvu3eSXxMZlY5PqZBTkvri1s9zgQ0uwRkK5r2VXNEoVmXCDnnEX/tX5EzzgNUQ== -"@metamask/controller-utils@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-4.3.2.tgz#e11fda4b2d38b96925891410665b57efcf41e07d" - integrity sha512-QlBUfUbexB+7IXHtZzFVYqKUvQEvktCHoDrzkXN8pjCbjm4sZtB6Lh47O0QSyoPR3dT77mphTz3GktjmfzskbA== - dependencies: - "@metamask/eth-query" "^3.0.1" - "@metamask/utils" "^6.2.0" - "@spruceid/siwe-parser" "1.1.3" - eth-ens-namehash "^2.0.8" - eth-rpc-errors "^4.0.2" - ethereumjs-util "^7.0.10" - ethjs-unit "^0.1.6" - fast-deep-equal "^3.1.3" - "@metamask/controller-utils@^5.0.0", "@metamask/controller-utils@^5.0.1", "@metamask/controller-utils@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-5.0.2.tgz#f6e848d9b80aca7943e1edae927324982305d1f8" @@ -4036,6 +4027,25 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-engine@^8.0.1": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz#29510a871a8edef892f838ee854db18de0bf0d14" + integrity sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + +"@metamask/json-rpc-middleware-stream@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.1.tgz#3e10c93c88507b1a55eea5d125ebf87db0f8fead" + integrity sha512-hsveICXi/56do/mxgwE4IApWwOfZ204iWtSiCcLayEDCLS96X/tqnW1xXvNTrk1l4PtSUHajsyHBY67I89bTIA== + dependencies: + "@metamask/json-rpc-engine" "^8.0.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + readable-stream "^3.6.2" + "@metamask/key-tree@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-9.0.0.tgz#ce880a79f35af5b3b540b44be16ff98cc77be4c1" @@ -4091,16 +4101,25 @@ ethereumjs-wallet "^1.0.1" immer "^9.0.6" -"@metamask/logging-controller@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@metamask/logging-controller/-/logging-controller-1.0.1.tgz#63aa402e6213d3056ab43e272e0029c24958885d" - integrity sha512-nRe7Ci0E2IP3plL2Xh172nvtDR9hJlMZZ2TcAJADQtUFeUB5AVy0Wad3sgp5MWHGnWYIgEhbgMvRNXoFdvSXhA== +"@metamask/logging-controller@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@metamask/logging-controller/-/logging-controller-1.0.4.tgz#78046f9ec70840fc6d8b96ab18a66b69a42f635f" + integrity sha512-L5Ruq3fD5PnDEQmg2+3B2zrzpZr0zrdNvTqA95i4/cQi8KP+zL9Xf2bFd0fBam8tda5lrkpVK7l/tEV2tF3GkA== dependencies: - "@metamask/base-controller" "^3.2.1" - "@metamask/controller-utils" "^4.3.2" + "@metamask/base-controller" "^3.2.3" + "@metamask/controller-utils" "^5.0.2" uuid "^8.3.2" -"@metamask/message-manager@^7.3.2", "@metamask/message-manager@^7.3.9": +"@metamask/logging-controller@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@metamask/logging-controller/-/logging-controller-2.0.3.tgz#bedd17902fc6ffc1c9c703e3156827866753710b" + integrity sha512-3wA65IaE/jPLnqxoFtoQhllNIH31AtU8K3IlcsUqfDRPG6faVUj7I+pEKPVjdYGN90rWnzO6Axx/90FGQtUi6Q== + dependencies: + "@metamask/base-controller" "^4.1.1" + "@metamask/controller-utils" "^8.0.4" + uuid "^8.3.2" + +"@metamask/message-manager@^7.3.5", "@metamask/message-manager@^7.3.9": version "7.3.9" resolved "https://registry.yarnpkg.com/@metamask/message-manager/-/message-manager-7.3.9.tgz#4d416998bbdda30d80ebbb50b5efc7475b816ec9" integrity sha512-B0+Qa1bPGMuVdBDaq8zcAKkRXWAt1CYAXW2NXzfO85a2ePFCUIrIhF9W8LflatXDXqesE5Y8Dij0sHGmsUfF8w== @@ -4113,6 +4132,19 @@ jsonschema "^1.2.4" uuid "^8.3.2" +"@metamask/message-signing-snap@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@metamask/message-signing-snap/-/message-signing-snap-0.3.3.tgz#0c37814baa7bb58c2b8bc84caa08b247ba78067b" + integrity sha512-SXti71T79io4siBxgI1xBtQ1UTC3h6dpCcuip9ap/vXDpUhzPIBPMipPv5anLMR9jK2+lk+TgYCz9LZEee5Rqg== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/snaps-sdk" "^3.1.1" + "@metamask/utils" "^8.3.0" + "@noble/ciphers" "^0.5.1" + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + zod "^3.22.4" + "@metamask/metamask-eth-abis@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-3.0.0.tgz#eccc0746b3ab1ab63000444403819c16e88b5272" @@ -4303,10 +4335,10 @@ "@metamask/utils" "^8.1.0" readable-stream "3.6.2" -"@metamask/ppom-validator@0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@metamask/ppom-validator/-/ppom-validator-0.29.0.tgz#449106b8fc87f706a82a41d92b40c7ae95a1025d" - integrity sha512-1+/NF8B8mdtZazGqLIKbD/lEdCsc0F074zyJMxSPt/AE0anx0LCo0mY9KT6jKtglSjeDohlqUdfsTA6kcWkluA== +"@metamask/ppom-validator@0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@metamask/ppom-validator/-/ppom-validator-0.30.0.tgz#7c2c7ff9cd602acee1004ddc882e02cc8b70588c" + integrity sha512-MY1DK7Oa6sYZiXlssZpw3pUYhOeY/y2I3p69wyL/hS+MUOPDZmMaNPG1eapwBKmdq91D4oMSlTHg8GS4wzpmjw== dependencies: "@metamask/base-controller" "^3.0.0" "@metamask/controller-utils" "^8.0.1" @@ -4369,6 +4401,24 @@ readable-stream "^3.6.2" webextension-polyfill "^0.10.0" +"@metamask/providers@^16.0.0": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.1.0.tgz#7da593d17c541580fa3beab8d9d8a9b9ce19ea07" + integrity sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g== + dependencies: + "@metamask/json-rpc-engine" "^8.0.1" + "@metamask/json-rpc-middleware-stream" "^7.0.1" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.1.1" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + "@metamask/react-native-actionsheet@2.4.2": version "2.4.2" resolved "https://registry.yarnpkg.com/@metamask/react-native-actionsheet/-/react-native-actionsheet-2.4.2.tgz#9f956fe9e784d92c8e33656877fcfaabe4a482f1" @@ -4397,6 +4447,11 @@ uuid "3.3.2" validator "^7.0.0" +"@metamask/react-native-search-api@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@metamask/react-native-search-api/-/react-native-search-api-1.0.1.tgz#b3a2069ff851b88e1fe64b86f1fab7088a4daceb" + integrity sha512-ceCzyRZDZM0lpQSNUiFpo16nysTDIMUhkdyUcvBbkpiSUnYV+SjVMxYVn2Q+h7P9tce8wrb6zoxdlfK5BXtbAQ== + "@metamask/react-native-splash-screen@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@metamask/react-native-splash-screen/-/react-native-splash-screen-3.2.0.tgz#06a6547c143b088e47af40eacea9ac6657ac937f" @@ -4415,10 +4470,10 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q== -"@metamask/safe-event-emitter@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.0.0.tgz#8c2b9073fe0722d48693143b0dc8448840daa3bd" - integrity sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ== +"@metamask/safe-event-emitter@^3.0.0", "@metamask/safe-event-emitter@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.1.tgz#e89b840a7af8097a8ed4953d8dc8470d1302d3ef" + integrity sha512-ihb3B0T/wJm1eUuArYP4lCTSEoZsClHhuWyfo/kMX3m/odpqNcPfsz5O2A3NT7dXCAgWPGDQGPqygCpgeniKMw== "@metamask/scure-bip39@^2.1.0": version "2.1.0" @@ -4439,17 +4494,18 @@ utf-8-validate "^6.0.3" uuid "^8.3.2" -"@metamask/signature-controller@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-6.0.0.tgz#631a286816cc7aef56d7e8d67c36820999be9ef3" - integrity sha512-wNggr7S/2OK62hn5e11WM0IzWHjn/uE1K7R9+9TuDcumpC/uGIoQKAWQ21zUlBDnzWXj0NXt9YC43tm5VaBJGg== +"@metamask/signature-controller@6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-6.1.3.tgz#e24914d0589873f01e6d0ba198e98aa222aa6ae1" + integrity sha512-0mOTD/Ba/bxCSnrMR5SifklgB+Rz4TfTTQtU+0KYOel5Q2dDkZEB1A96iXC5gH+PZ7bK7wJWq/zjUmsIuRtHmQ== dependencies: - "@metamask/approval-controller" "^3.5.1" - "@metamask/base-controller" "^3.2.1" - "@metamask/controller-utils" "^4.3.2" - "@metamask/message-manager" "^7.3.2" - "@metamask/utils" "^6.2.0" - eth-rpc-errors "^4.0.2" + "@metamask/approval-controller" "^4.0.1" + "@metamask/base-controller" "^3.2.3" + "@metamask/controller-utils" "^5.0.2" + "@metamask/logging-controller" "^1.0.4" + "@metamask/message-manager" "^7.3.5" + "@metamask/rpc-errors" "^6.1.0" + "@metamask/utils" "^8.1.0" ethereumjs-util "^7.0.10" immer "^9.0.6" lodash "^4.17.21" @@ -4536,6 +4592,18 @@ fast-xml-parser "^4.3.4" superstruct "^1.0.3" +"@metamask/snaps-sdk@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@metamask/snaps-sdk/-/snaps-sdk-3.2.0.tgz#66d60869697a479a3484adc9532d82c3b568fc19" + integrity sha512-Xfsc6seyucs7TP2JLXoKYrWm5FbrttdHnMOTfuzTb4T+qmdmoc3wdw83RAGjRFiOOaHGFc6JJSCdP33fBmw4Hg== + dependencies: + "@metamask/key-tree" "^9.0.0" + "@metamask/providers" "^16.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/utils" "^8.3.0" + fast-xml-parser "^4.3.4" + superstruct "^1.0.3" + "@metamask/snaps-utils@^5.1.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-5.2.0.tgz#ff43b97ff176846230d8bdedb1769b269effc4d8" @@ -4714,13 +4782,25 @@ jsbi "^3.1.5" sha.js "^2.4.11" -"@noble/curves@1.3.0", "@noble/curves@^1.2.0", "@noble/curves@~1.3.0": +"@noble/ciphers@^0.5.1": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.2.tgz#879367fd51d59185259eb844d5b9a78f408b4a12" + integrity sha512-GADtQmZCdgbnNp+daPLc3OY3ibEtGGDV/+CzeM3MFnhiQ7ELQKlsHWYq0YbYUXx4jU3/Y1erAxU6r+hwpewqmQ== + +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== dependencies: "@noble/hashes" "1.3.3" +"@noble/curves@^1.2.0", "@noble/curves@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/ed25519@^1.6.0": version "1.7.3" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123" @@ -4731,7 +4811,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@^1.0.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.2": +"@noble/hashes@1.4.0", "@noble/hashes@^1.0.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.2", "@noble/hashes@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== @@ -12351,6 +12431,16 @@ builtins@^5.0.0, builtins@^5.0.1: dependencies: semver "^7.0.0" +bunyamin@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/bunyamin/-/bunyamin-1.6.2.tgz#27e9025055fe3c76daaa46caf9b2bdd9fd514664" + integrity sha512-qU250X23tV8n1vh/M94nZfAA8S960N93eZKILY2yvYr8Q+dcPHB+jK9/BHmTcaAHsIsdcVf8vCVznb4zUCg0fw== + dependencies: + "@flatten-js/interval-tree" "^1.1.2" + multi-sort-stream "^1.0.4" + stream-json "^1.7.5" + trace-event-lib "^1.3.1" + bunyan-debug-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-3.1.0.tgz#78309c67ad85cfb8f011155334152c49209dcda8" @@ -12368,6 +12458,18 @@ bunyan@^1.8.12: mv "~2" safe-json-stringify "~1" +bunyan@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-2.0.5.tgz#9dd056755220dddd8b5bb9cf76f3d0d766e96e71" + integrity sha512-Jvl74TdxCN6rSP9W1I6+UOUtwslTDqsSFkDqZlFb/ilaSvQ+bZAnXT/GT97IZ5L+Vph0joPZPhxUyn6FLNmFAA== + dependencies: + exeunt "1.1.0" + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.19.3" + mv "~2" + safe-json-stringify "~1" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -14296,10 +14398,10 @@ detect-node-es@^1.1.0: resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== -detox@^20.14.3: - version "20.14.3" - resolved "https://registry.yarnpkg.com/detox/-/detox-20.14.3.tgz#34e5b5301dcf2f1924c71c3aa530e21cebac1bf9" - integrity sha512-GaLXanEaRm6ROgNOiaaVTbD3mNNf4N5z5pHsyvbbtjwqS9V/c6RYE7V3NOyXgNmQ1dYalo9Ml/ZdeQtarsNV0w== +detox@^20.20.3: + version "20.20.3" + resolved "https://registry.yarnpkg.com/detox/-/detox-20.20.3.tgz#c39470523b1a75c4dc895666c5d749978978e42e" + integrity sha512-sca2z+6SYnA5+9jF1pf/t4RBOcvnBO0vmsTtrmnpej76Q5rhX0acZpFbDgvQQPGKu/TDdH+KFYK0tQ2VrTNY1w== dependencies: ajv "^8.6.3" bunyan "^1.8.12" @@ -14313,6 +14415,7 @@ detox@^20.14.3: funpermaproxy "^1.1.0" glob "^8.0.3" ini "^1.3.4" + jest-environment-emit "^1.0.5" json-cycle "^1.3.0" lodash "^4.17.11" multi-sort-stream "^1.0.3" @@ -16338,6 +16441,11 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" +exeunt@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/exeunt/-/exeunt-1.1.0.tgz#af72db6f94b3cb75e921aee375d513049843d284" + integrity sha512-dd++Yn/0Fp+gtJ04YHov7MeAii+LFivJc6KqnJNfplzLVUkUDrfKoQDTLlCgzcW15vY5hKlHasWeIsQJ8agHsw== + exif-parser@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" @@ -19097,6 +19205,20 @@ jest-each@^29.7.0: jest-util "^29.7.0" pretty-format "^29.7.0" +jest-environment-emit@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/jest-environment-emit/-/jest-environment-emit-1.0.7.tgz#14c2197e9fa48affa25b15fc5f4a74690aea7efd" + integrity sha512-/0AYqbL3zrfRTtGyzTZwgRxQZiDXEM8ZUfY7Uscla/XGs9vszx4f0XTSZqAk3CQaiwYAoKvFZkB2vSKm1Q08fQ== + dependencies: + bunyamin "^1.5.2" + bunyan "^2.0.5" + bunyan-debug-stream "^3.1.0" + funpermaproxy "^1.1.0" + lodash.merge "^4.6.2" + node-ipc "9.2.1" + strip-ansi "^6.0.0" + tslib "^2.5.3" + jest-environment-node@^29.2.1, jest-environment-node@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" @@ -21470,7 +21592,7 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multi-sort-stream@^1.0.3: +multi-sort-stream@^1.0.3, multi-sort-stream@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/multi-sort-stream/-/multi-sort-stream-1.0.4.tgz#e4348edc9edc36e16333e531a90c0f166235cc99" integrity sha512-hAZ8JOEQFbgdLe8HWZbb7gdZg0/yAIHF00Qfo3kd0rXFv96nXe+/bPTrKHZ2QMHugGX4FiAyET1Lt+jiB+7Qlg== @@ -24294,10 +24416,6 @@ react-native-scrollable-tab-view@^1.0.0: prop-types "^15.6.0" react-timer-mixin "^0.13.3" -react-native-search-api@ombori/react-native-search-api#8/head: - version "1.0.1" - resolved "https://codeload.github.com/ombori/react-native-search-api/tar.gz/08dbdf1ffd53801057662aa3207c11d2f78e8a94" - react-native-sensors@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/react-native-sensors/-/react-native-sensors-5.3.0.tgz#8c89541f80a68c185ff2f7059c3078f4975c730c" @@ -26267,10 +26385,10 @@ stream-combiner@~0.0.4: dependencies: duplexer "~0.1.1" -stream-json@^1.7.4: - version "1.7.5" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.7.5.tgz#2ff0563011f22cea4f6a28dbfc0344a53c761fe4" - integrity sha512-NSkoVduGakxZ8a+pTPUlcGEeAGQpWL9rKJhOFCV+J/QtdQUEU5vtBgVg6eJXn8JB8RZvpbJWZGvXkhz70MLWoA== +stream-json@^1.7.4, stream-json@^1.7.5: + version "1.8.0" + resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.8.0.tgz#53f486b2e3b4496c506131f8d7260ba42def151c" + integrity sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw== dependencies: stream-chain "^2.2.5" @@ -27117,7 +27235,7 @@ tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.3: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -28852,6 +28970,11 @@ zip-stream@^4.1.0: compress-commons "^4.1.0" readable-stream "^3.6.0" +zod@^3.22.4: + version "3.23.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.6.tgz#c08a977e2255dab1fdba933651584a05fcbf19e1" + integrity sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA== + zxcvbn@4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"