diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2bfe0ef07544..c45cf619f746 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -92,6 +92,15 @@ "advanced": { "message": "Advanced" }, + "agreeTermsOfService": { + "message": "I agree to the Terms of Service" + }, + "allDone": { + "message": "All Done" + }, + "alreadyHaveSeedPhrase": { + "message": "No, I already have a seed phrase" + }, "amount": { "message": "Amount" }, @@ -239,6 +248,9 @@ "confirmTransaction": { "message": "Confirm Transaction" }, + "congratulations": { + "message": "Congratulations" + }, "connectHardwareWallet": { "message": "Connect Hardware Wallet" }, @@ -326,6 +338,9 @@ "createAccount": { "message": "Create Account" }, + "createAWallet": { + "message": "Create a Wallet" + }, "createDen": { "message": "Create" }, @@ -448,6 +463,24 @@ "encryptNewDen": { "message": "Encrypt your new DEN" }, + "endOfFlowMessage1": { + "message": "You passed the test - keep your seedphrase safe, it's your responsibility!" + }, + "endOfFlowMessage2": { + "message": "Tips on storing it safely" + }, + "endOfFlowMessage3": { + "message": "Save a backup in multiple places" + }, + "endOfFlowMessage4": { + "message": "Never tell anyone" + }, + "endOfFlowMessage5": { + "message": "If you need to back your seed phrase again, you can find it in Settings -> Security." + }, + "endOfFlowMessage6": { + "message": "MetaMask cannot recover your seedphrase. Learn more." + }, "ensNameNotFound": { "message": "ENS name not found" }, @@ -581,10 +614,16 @@ "getHelp": { "message": "Get Help." }, + "getStarted": { + "message": "Get Started" + }, "greaterThanMin": { "message": "must be greater than or equal to $1.", "description": "helper for inputting hex as decimal input" }, + "happyToSeeYou": { + "message": "We’re happy to see you." + }, "hardware": { "message": "hardware" }, @@ -647,6 +686,9 @@ "importDen": { "message": "Import Existing DEN" }, + "importWallet": { + "message": "Import Wallet" + }, "imported": { "message": "Imported", "description": "status showing that an account has been fully loaded into the keyring" @@ -731,6 +773,9 @@ "message": "must be less than or equal to $1.", "description": "helper for inputting hex as decimal input" }, + "letsGoSetUp": { + "message": "Yes, let’s get set up!" + }, "likeToAddTokens": { "message": "Would you like to add these tokens?" }, @@ -777,7 +822,7 @@ "message": "Message" }, "metamaskDescription": { - "message": "MetaMask is a secure identity vault for Ethereum." + "message": "Connecting you to Ethereum and the Decentralized Web." }, "metamaskSeedWords": { "message": "MetaMask Seed Words" @@ -848,6 +893,21 @@ "newNetwork": { "message": "New Network" }, + "newToMetaMask": { + "message": "New to MetaMask?" + }, + "noAlreadyHaveSeed": { + "message": "No, I already have a seed phrase" + }, + "protectYourKeys": { + "message": "Protect Your Keys!" + }, + "protectYourKeysMessage1": { + "message": "Be careful with your seed phrase — there have been reports of websites that attempt to imitate MetaMask. MetaMask will never ask for your seed phrase!" + }, + "protectYourKeysMessage2": { + "message": "Keep your phrase safe. If you see something fishy, or you’re uncertain about a website, email support@metamask.io" + }, "rpcURL": { "message": "New RPC URL" }, @@ -1603,6 +1663,9 @@ "yourUniqueAccountImageDescription2": { "message": "You’ll see this image everytime you need to confirm a transaction." }, + "yourUniqueAccountImageDescription3": { + "message": "MetaMask will never ask for your seed phrase!" + }, "zeroGasPriceOnSpeedUpError": { "message":"Zero gas price on speed up" } diff --git a/app/images/download-alt.svg b/app/images/download-alt.svg new file mode 100644 index 000000000000..11c54fd66171 --- /dev/null +++ b/app/images/download-alt.svg @@ -0,0 +1,5 @@ + diff --git a/app/images/sleuth.svg b/app/images/sleuth.svg new file mode 100644 index 000000000000..f4ac8c787c51 --- /dev/null +++ b/app/images/sleuth.svg @@ -0,0 +1 @@ + diff --git a/app/images/thin-plus.svg b/app/images/thin-plus.svg new file mode 100644 index 000000000000..f46c72a4a3f6 --- /dev/null +++ b/app/images/thin-plus.svg @@ -0,0 +1,4 @@ + diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index d4a3805847cf..ad3e8b1fe2a1 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -65,13 +65,16 @@ describe('Using MetaMask with an existing account', function () { beforeEach(async function () { await driver.executeScript( + 'window.origFetch = window.fetch.bind(window);' + 'window.fetch = ' + '(...args) => { ' + 'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + '(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + - 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } ' + - 'return window.fetch(...args); }' + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + + '(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + + 'return window.origFetch(...args); }' ) }) @@ -95,16 +98,19 @@ describe('Using MetaMask with an existing account', function () { describe('First time flow starting from an existing seed phrase', () => { it('clicks the continue button on the welcome screen', async () => { - const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button')) + await findElement(driver, By.css('.welcome-page__header')) + const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button')) welcomeScreenBtn.click() await delay(largeDelayMs) }) - it('imports a seed phrase', async () => { - const [seedPhrase] = await findElements(driver, By.xpath(`//a[contains(text(), 'Import with seed phrase')]`)) - await seedPhrase.click() - await delay(regularDelayMs) + it('clicks the "Import Wallet" option', async () => { + const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) + customRpcButton.click() + await delay(largeDelayMs) + }) + it('imports a seed phrase', async () => { const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) await seedTextArea.sendKeys(testSeedPhrase) await delay(regularDelayMs) @@ -114,39 +120,25 @@ describe('Using MetaMask with an existing account', function () { const [confirmPassword] = await findElements(driver, By.id('confirm-password')) confirmPassword.sendKeys('correct horse battery staple') + const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) + await tosCheckBox.click() + const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) await importButton.click() await delay(regularDelayMs) }) - it('clicks through the ToS', async () => { - // terms of use - await findElement(driver, By.css('.first-time-flow__markdown')) - const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled() - assert.equal(canClickThrough, false, 'disabled continue button') - const bottomOfTos = await findElement(driver, By.linkText('Attributions')) - await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) - await delay(regularDelayMs) - const acceptTos = await findElement(driver, By.css('button.first-time-flow__button')) - driver.wait(until.elementIsEnabled(acceptTos)) - await acceptTos.click() - await delay(regularDelayMs) - }) - - it('clicks through the privacy notice', async () => { - // privacy notice + it('clicks through the security warning screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`)) const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) await nextScreen.click() await delay(regularDelayMs) }) - it('clicks through the phishing notice', async () => { - // phishing notice - const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown')) - await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) - await delay(regularDelayMs) - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() + it('clicks through the success screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) + const doneButton = await findElement(driver, By.css('button.first-time-flow__button')) + await doneButton.click() await delay(regularDelayMs) }) }) diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js index 099ed944e97f..781811d6d3f3 100644 --- a/test/e2e/beta/metamask-beta-responsive-ui.spec.js +++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js @@ -80,134 +80,111 @@ describe('MetaMask', function () { }) describe('Going through the first time flow', () => { - it('clicks the continue button on the welcome screen', async () => { - const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button')) - welcomeScreenBtn.click() - await delay(largeDelayMs) - }) - - it('accepts a secure password', async () => { - const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) - const button = await findElement(driver, By.css('.first-time-flow__form button')) - - await passwordBox.sendKeys('correct horse battery staple') - await passwordBoxConfirm.sendKeys('correct horse battery staple') - await button.click() - await delay(regularDelayMs) - }) - - it('clicks through the unique image screen', async () => { - await findElement(driver, By.css('.first-time-flow__unique-image')) - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - }) - - it('clicks through the ToS', async () => { - // terms of use - await findElement(driver, By.css('.first-time-flow__markdown')) - const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled() - assert.equal(canClickThrough, false, 'disabled continue button') - const bottomOfTos = await findElement(driver, By.linkText('Attributions')) - await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) - await delay(regularDelayMs) - const acceptTos = await findElement(driver, By.css('button.first-time-flow__button')) - driver.wait(until.elementIsEnabled(acceptTos)) - await acceptTos.click() - await delay(regularDelayMs) - }) - - it('clicks through the privacy notice', async () => { - // privacy notice - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - }) - - it('clicks through the phishing notice', async () => { - // phishing notice - const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown')) - await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) - await delay(regularDelayMs) - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - }) - - let seedPhrase - - it('reveals the seed phrase', async () => { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') - await driver.wait(until.elementLocated(byRevealButton, 10000)) - const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) - await revealSeedPhraseButton.click() - await delay(regularDelayMs) - - seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText() - assert.equal(seedPhrase.split(' ').length, 12) - await delay(regularDelayMs) - - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - }) - - async function clickWordAndWait (word) { - const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]` - const word0 = await findElement(driver, By.xpath(xpath), 10000) - - await word0.click() - await delay(tinyDelayMs) - } + it('clicks the continue button on the welcome screen', async () => { + await findElement(driver, By.css('.welcome-page__header')) + const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button')) + welcomeScreenBtn.click() + await delay(largeDelayMs) + }) - async function retypeSeedPhrase (words, wasReloaded, count = 0) { - try { - if (wasReloaded) { - const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') - await driver.wait(until.elementLocated(byRevealButton, 10000)) - const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) - await revealSeedPhraseButton.click() - await delay(regularDelayMs) - - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - } + it('clicks the "Create New Wallet" option', async () => { + const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + customRpcButton.click() + await delay(largeDelayMs) + }) + + it('accepts a secure password', async () => { + const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) + const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) + const button = await findElement(driver, By.css('.first-time-flow__form button')) + + await passwordBox.sendKeys('correct horse battery staple') + await passwordBoxConfirm.sendKeys('correct horse battery staple') + + const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) + await tosCheckBox.click() + + await button.click() + await delay(regularDelayMs) + }) + + it('clicks through the security warning screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`)) + const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + let seedPhrase + + it('reveals the seed phrase', async () => { + const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + await driver.wait(until.elementLocated(byRevealButton, 10000)) + const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) + await revealSeedPhraseButton.click() + await delay(regularDelayMs) + + seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + await delay(regularDelayMs) + + const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + async function clickWordAndWait (word) { + const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]` + const word0 = await findElement(driver, By.xpath(xpath), 10000) + + await word0.click() + await delay(tinyDelayMs) + } - for (let i = 0; i < 12; i++) { - await clickWordAndWait(words[i]) - } - } catch (e) { - if (count > 2) { - throw e - } else { - await loadExtension(driver, extensionId) - await retypeSeedPhrase(words, true, count + 1) + async function retypeSeedPhrase (words, wasReloaded, count = 0) { + try { + if (wasReloaded) { + const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button') + await driver.wait(until.elementLocated(byRevealButton, 10000)) + const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) + await revealSeedPhraseButton.click() + await delay(regularDelayMs) + + const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) + await nextScreen.click() + await delay(regularDelayMs) + } + + for (let i = 0; i < 12; i++) { + await clickWordAndWait(words[i]) + } + } catch (e) { + if (count > 2) { + throw e + } else { + await loadExtension(driver, extensionId) + await retypeSeedPhrase(words, true, count + 1) + } } } - } - it('can retype the seed phrase', async () => { - const words = seedPhrase.split(' ') + it('can retype the seed phrase', async () => { + const words = seedPhrase.split(' ') - await retypeSeedPhrase(words) - await delay(regularDelayMs) + await retypeSeedPhrase(words) - const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) - await confirm.click() - await delay(regularDelayMs) - }) + const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirm.click() + await delay(regularDelayMs) + }) - it('clicks through the deposit modal', async () => { - const byBuyModal = By.css('span .modal') - const buyModal = await driver.wait(until.elementLocated(byBuyModal)) - const closeModal = await findElement(driver, By.css('.page-container__header-close')) - await closeModal.click() - await driver.wait(until.stalenessOf(buyModal)) - await delay(regularDelayMs) + it('clicks through the success screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) + const doneButton = await findElement(driver, By.css('button.first-time-flow__button')) + await doneButton.click() + await delay(regularDelayMs) + }) }) - }) describe('Show account information', () => { it('show account details dropdown menu', async () => { diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index bde031301f22..fdd13bd0b174 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -102,11 +102,18 @@ describe('MetaMask', function () { describe('Going through the first time flow', () => { it('clicks the continue button on the welcome screen', async () => { - const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button')) + await findElement(driver, By.css('.welcome-page__header')) + const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button')) welcomeScreenBtn.click() await delay(largeDelayMs) }) + it('clicks the "Create New Wallet" option', async () => { + const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) + customRpcButton.click() + await delay(largeDelayMs) + }) + it('accepts a secure password', async () => { const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) @@ -114,43 +121,16 @@ describe('MetaMask', function () { await passwordBox.sendKeys('correct horse battery staple') await passwordBoxConfirm.sendKeys('correct horse battery staple') - await button.click() - await delay(regularDelayMs) - }) - - it('clicks through the unique image screen', async () => { - await findElement(driver, By.css('.first-time-flow__unique-image')) - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() - await delay(regularDelayMs) - }) - it('clicks through the ToS', async () => { - // terms of use - await findElement(driver, By.css('.first-time-flow__markdown')) - const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled() - assert.equal(canClickThrough, false, 'disabled continue button') - const bottomOfTos = await findElement(driver, By.linkText('Attributions')) - await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) - await delay(regularDelayMs) - const acceptTos = await findElement(driver, By.css('button.first-time-flow__button')) - driver.wait(until.elementIsEnabled(acceptTos)) - await acceptTos.click() - await delay(regularDelayMs) - }) + const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) + await tosCheckBox.click() - it('clicks through the privacy notice', async () => { - // privacy notice - const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) - await nextScreen.click() + await button.click() await delay(regularDelayMs) }) - it('clicks through the phishing notice', async () => { - // phishing notice - const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown')) - await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) - await delay(regularDelayMs) + it('clicks through the security warning screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`)) const nextScreen = await findElement(driver, By.css('button.first-time-flow__button')) await nextScreen.click() await delay(regularDelayMs) @@ -219,12 +199,10 @@ describe('MetaMask', function () { await delay(regularDelayMs) }) - it('clicks through the deposit modal', async () => { - const byBuyModal = By.css('span .modal') - const buyModal = await driver.wait(until.elementLocated(byBuyModal)) - const closeModal = await findElement(driver, By.css('.page-container__header-close')) - await closeModal.click() - await driver.wait(until.stalenessOf(buyModal)) + it('clicks through the success screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) + const doneButton = await findElement(driver, By.css('button.first-time-flow__button')) + await doneButton.click() await delay(regularDelayMs) }) }) diff --git a/ui/app/components/pages/first-time-flow/create-password/create-password.component.js b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js index 69b1e549f919..7cca82ca60c9 100644 --- a/ui/app/components/pages/first-time-flow/create-password/create-password.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js @@ -8,13 +8,13 @@ import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, INITIALIZE_UNIQUE_IMAGE_ROUTE, - INITIALIZE_NOTICE_ROUTE, } from '../../../../routes' export default class CreatePassword extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, + isImportedKeyring: PropTypes.bool, onCreateNewAccount: PropTypes.func, onCreateNewAccountFromSeed: PropTypes.func, } @@ -23,17 +23,38 @@ export default class CreatePassword extends PureComponent { const { isInitialized, history } = this.props if (isInitialized) { - history.push(INITIALIZE_NOTICE_ROUTE) + history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) } } render () { - const { onCreateNewAccount, onCreateNewAccountFromSeed } = this.props + const { onCreateNewAccount, onCreateNewAccountFromSeed, isImportedKeyring } = this.props return (