diff --git a/packages/fxa-content-server/app/scripts/lib/app-start.js b/packages/fxa-content-server/app/scripts/lib/app-start.js index c55cf308972..966965fb553 100644 --- a/packages/fxa-content-server/app/scripts/lib/app-start.js +++ b/packages/fxa-content-server/app/scripts/lib/app-start.js @@ -44,7 +44,7 @@ import Session from './session'; import Storage from './storage'; import StorageMetrics from './storage-metrics'; import SupplicantRelier from '../models/reliers/pairing/supplicant'; -import SyncRelier from '../models/reliers/sync'; +import BrowserRelier from '../models/reliers/browser'; import Translator from './translator'; import UniqueUserId from '../models/unique-user-id'; import Url from './url'; @@ -269,8 +269,12 @@ Start.prototype = { window: this._window, } ); - } else if (this._isServiceSync()) { - relier = new SyncRelier( + } else if ( + this._isServiceSync() || + // context v3 is able to sign in to Firefox without enabling Sync + this._searchParam('context') === Constants.FX_DESKTOP_V3_CONTEXT + ) { + relier = new BrowserRelier( { context }, { isVerification: this._isVerification(), diff --git a/packages/fxa-content-server/app/scripts/lib/router.js b/packages/fxa-content-server/app/scripts/lib/router.js index 502bfba56c4..7dac6dbdf45 100644 --- a/packages/fxa-content-server/app/scripts/lib/router.js +++ b/packages/fxa-content-server/app/scripts/lib/router.js @@ -58,6 +58,7 @@ import SubscriptionsProductRedirectView from '../views/subscriptions_product_red import SubscriptionsManagementRedirectView from '../views/subscriptions_management_redirect'; import TwoStepAuthenticationView from '../views/settings/two_step_authentication'; import VerificationReasons from './verification-reasons'; +import WouldYouLikeToSync from '../views/would_you_like_to_sync'; import WhyConnectAnotherDeviceView from '../views/why_connect_another_device'; function getView(ViewOrPath) { @@ -278,6 +279,7 @@ const Router = Backbone.Router.extend({ 'verify_secondary_email(/)': createViewHandler(CompleteSignUpView, { type: VerificationReasons.SECONDARY_EMAIL_VERIFIED, }), + 'would_you_like_to_sync(/)': createViewHandler(WouldYouLikeToSync), }, initialize(options = {}) { diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js index 710f41b05aa..8e34706e229 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js @@ -23,6 +23,7 @@ const ALLOWED_LOGIN_FIELDS = [ 'keyFetchToken', 'offeredSyncEngines', 'sessionToken', + 'services', 'uid', 'unwrapBKey', 'verified', @@ -273,11 +274,39 @@ const FxSyncChannelAuthenticationBroker = FxSyncAuthenticationBroker.extend( * @private */ _getLoginData(account) { - const loginData = account.pick(ALLOWED_LOGIN_FIELDS); + let loginData = account.pick(ALLOWED_LOGIN_FIELDS) || {}; + const isMultiService = this.relier && this.relier.get('multiService'); + if (isMultiService) { + loginData = this._formatForMultiServiceBrowser(loginData); + } + loginData.verified = !!loginData.verified; loginData.verifiedCanLinkAccount = !!this._verifiedCanLinkEmail; return _.omit(loginData, _.isUndefined); }, + + /** + * if browser is multi service capable then we should send 'sync' properties + * in a different format + * @param loginData + * @private + */ + _formatForMultiServiceBrowser(loginData) { + loginData.services = {}; + if (!this.relier.get('doNotSync') && loginData.offeredSyncEngines) { + // make sure to NOT send an empty 'sync' object, + // this can happen in the `force_auth` flow. + loginData.services.sync = { + offeredEngines: loginData.offeredSyncEngines, + declinedEngines: loginData.declinedSyncEngines, + }; + } + // these should not be sent to a multi-service capable browser + delete loginData.offeredSyncEngines; + delete loginData.declinedSyncEngines; + + return loginData; + }, }, { REQUIRED_LOGIN_FIELDS, diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js index e9882ca06d3..87b3eb84178 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js @@ -52,6 +52,19 @@ export default BaseAuthenticationBroker.extend({ */ onFxaStatus(response = {}) { const syncEngines = this.get('chooseWhatToSyncWebV1Engines'); + const multiService = + response.capabilities && response.capabilities.multiService; + this.relier.set('multiService', multiService); + if (multiService) { + // we get the OAuth client id for the browser + // in order to replicate the uses of the 'service' param on the backend. + // See: https://github.com/mozilla/fxa/issues/2396#issuecomment-530662772 + if (!this.relier.has('service')) { + // the service in the query parameter currently overrides the status message + // this is due to backwards compatibility + this.relier.set('service', response.clientId); + } + } const additionalEngineIds = response.capabilities && response.capabilities.engines; if (syncEngines && additionalEngineIds) { diff --git a/packages/fxa-content-server/app/scripts/models/reliers/base.js b/packages/fxa-content-server/app/scripts/models/reliers/base.js index a6b02aff4dd..da9d8625186 100644 --- a/packages/fxa-content-server/app/scripts/models/reliers/base.js +++ b/packages/fxa-content-server/app/scripts/models/reliers/base.js @@ -12,6 +12,7 @@ import Backbone from 'backbone'; var Relier = Backbone.Model.extend({ defaults: { context: null, + name: 'base', }, fetch() { @@ -36,6 +37,15 @@ var Relier = Backbone.Model.extend({ return false; }, + /** + * Check if the relier should offer Sync + * + * @returns {Boolean} + */ + shouldOfferToSync() { + return false; + }, + /** * Check if the relier wants access to the account encryption keys. * diff --git a/packages/fxa-content-server/app/scripts/models/reliers/sync.js b/packages/fxa-content-server/app/scripts/models/reliers/browser.js similarity index 85% rename from packages/fxa-content-server/app/scripts/models/reliers/sync.js rename to packages/fxa-content-server/app/scripts/models/reliers/browser.js index 0d8cf16f6a8..fc739931319 100644 --- a/packages/fxa-content-server/app/scripts/models/reliers/sync.js +++ b/packages/fxa-content-server/app/scripts/models/reliers/browser.js @@ -34,6 +34,9 @@ const QUERY_PARAMETER_SCHEMA = { export default Relier.extend({ defaults: _.extend({}, Relier.prototype.defaults, { action: undefined, + name: 'browser', + doNotSync: false, + multiService: false, signinCode: undefined, tokenCode: false, }), @@ -50,6 +53,9 @@ export default Relier.extend({ if (this.get('service')) { this.set('serviceName', t('Firefox Sync')); + } else { + // if no service provided, then we are just signing into the browser + this.set('serviceName', t('Firefox')); } }); }, @@ -58,6 +64,10 @@ export default Relier.extend({ return true; }, + shouldOfferToSync(viewName) { + return this.get('service') !== 'sync' && viewName !== 'force-auth'; + }, + /** * Desktop clients will always want keys so they can sync. * diff --git a/packages/fxa-content-server/app/scripts/models/reliers/oauth.js b/packages/fxa-content-server/app/scripts/models/reliers/oauth.js index 53698dc19af..61ccc7e3131 100644 --- a/packages/fxa-content-server/app/scripts/models/reliers/oauth.js +++ b/packages/fxa-content-server/app/scripts/models/reliers/oauth.js @@ -85,6 +85,7 @@ var OAuthRelier = Relier.extend({ acrValues: null, clientId: null, context: Constants.OAUTH_CONTEXT, + name: 'oauth', keysJwk: null, // permissions are individual scopes permissions: null, diff --git a/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js b/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js index 4699f719b87..fd3e8d8fa33 100644 --- a/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js +++ b/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js @@ -17,6 +17,8 @@ const AUTHORITY_QUERY_PARAM_SCHEMA = { /*eslint-enable camelcase, sorting/sort-object-props*/ export default class AuthorityRelier extends OAuthRelier { + name = 'pairing-authority'; + fetch() { return Promise.resolve().then(() => { this.importSearchParamsUsingSchema( diff --git a/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js b/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js index be4f2eeedc8..337cc831e9d 100644 --- a/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js +++ b/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js @@ -44,6 +44,8 @@ const SUPPLICANT_HASH_PARAMETER_SCHEMA = { /*eslint-enable camelcase, sorting/sort-object-props*/ export default class SupplicantRelier extends OAuthRelier { + name = 'pairing-supplicant'; + fetch() { return Promise.resolve().then(() => { this.importHashParamsUsingSchema( diff --git a/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache b/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache index 19b06d231cb..5963b9ebe27 100644 --- a/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache +++ b/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache @@ -1,5 +1,4 @@
-
{{#t}}%(email)s registered{{/t}}
@@ -17,9 +16,14 @@
{{/engines}} -
- +
+ +
+ {{#showDoNotSyncButton}} + + {{/showDoNotSyncButton}}
diff --git a/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache b/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache new file mode 100644 index 00000000000..2a6bdde9182 --- /dev/null +++ b/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache @@ -0,0 +1,20 @@ +
+
+
+ +
+
+
+

+ {{#t}}Would you like to sync this device to your account?{{/t}} +

+
+
+ +
+ +
+
+
diff --git a/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js b/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js index 456dcd448ec..c92169c7205 100644 --- a/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js +++ b/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js @@ -6,6 +6,7 @@ import _ from 'underscore'; import $ from 'jquery'; import BackMixin from './mixins/back-mixin'; import Cocktail from 'cocktail'; +import DoNotSyncMixin from './mixins/do-not-sync-mixin'; import FlowEventsMixin from './mixins/flow-events-mixin'; import FormView from './form'; import SessionVerificationPollMixin from './mixins/session-verification-poll-mixin'; @@ -19,7 +20,7 @@ const View = FormView.extend( template: Template, className: 'choose-what-to-sync', - initialize(options = {}) { + initialize() { // Account data is passed in from sign up flow. this._account = this.user.initAccount(this.model.get('account')); @@ -27,6 +28,13 @@ const View = FormView.extend( // a continuation function is passed in that should be called // when submit has completed. this.onSubmitComplete = this.model.get('onSubmitComplete'); + + // we only want to show the do not sync button for multi-service browser, + // that are not logging into sync and that came from a sign up flow. + this.allowToDisableSync = this.model.get('allowToDisableSync'); + // in some cases where the user has a choice to decline Sync + // we don't want to poll for verification + this.pollVerification = this.model.get('pollVerification'); }, getAccount() { @@ -59,9 +67,13 @@ const View = FormView.extend( // See #5554 .then(() => this.broker.persistVerificationData(account)) .then(() => { - this.waitForSessionVerification(account, () => - this.validateAndSubmit() - ); + if (this.pollVerification) { + // we should only wait for session verification here + // if the user doesn't have an option to disable sync + this.waitForSessionVerification(account, () => + this.validateAndSubmit() + ); + } }) ); }, @@ -72,12 +84,13 @@ const View = FormView.extend( }, setInitialContext(context) { - var account = this.getAccount(); + const account = this.getAccount(); const engines = this._getOfferedEngines(); context.set({ email: account.get('email'), engines, + showDoNotSyncButton: this.allowToDisableSync, }); }, @@ -166,6 +179,12 @@ const View = FormView.extend( } ); -Cocktail.mixin(View, BackMixin, FlowEventsMixin, SessionVerificationPollMixin); +Cocktail.mixin( + View, + BackMixin, + FlowEventsMixin, + SessionVerificationPollMixin, + DoNotSyncMixin +); export default View; diff --git a/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js new file mode 100644 index 00000000000..591ccabd11a --- /dev/null +++ b/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * View mixin to handle the Do Not Sync button in views. + * + * @mixin DoNotSync mixin + */ +import SigninMixin from './signin-mixin'; + +export default { + dependsOn: [SigninMixin], + + events: { + 'click #do-not-sync-device': 'doNotSync', + }, + + doNotSync() { + this.relier.set('doNotSync', true); + return Promise.resolve().then(() => { + const account = this.getAccount(); + return this.onSubmitComplete(account); + }); + }, +}; diff --git a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js index 13045846f40..68435255412 100644 --- a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js +++ b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js @@ -104,6 +104,18 @@ export default { }); } + if (this.relier.shouldOfferToSync(this.viewName)) { + // flows that are a part of the 'browser' relier which + // do not pass a service get asked of they want to Sync + + // force_auth attempts do not a choice for Sync + return this.navigate('would_you_like_to_sync', { + account: account, + // propagate the onSubmitComplete to choose_what_to_sync screen if needed + onSubmitComplete: this.onSignInSuccess.bind(this), + }); + } + if (typeof options.onSuccess === 'function') { options.onSuccess(); } diff --git a/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js index 79fcdf3787d..815d9f73bb6 100644 --- a/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js +++ b/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js @@ -60,6 +60,8 @@ export default { } else if (this.broker.get('chooseWhatToSyncWebV1Engines')) { return this.navigate('choose_what_to_sync', { account: account, + allowToDisableSync: this.relier.get('service') !== 'sync', + pollVerification: this.relier.get('service') === 'sync', // choose_what_to_sync screen will call onSubmitComplete // with an updated account onSubmitComplete: onSubmitComplete, diff --git a/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js index a55580815cd..df318363eef 100644 --- a/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js +++ b/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js @@ -74,7 +74,9 @@ export default function(config) { * @returns {Boolean} */ isSyncSuggestionEnabled() { - return !this.relier.get('service'); + return ( + !this.relier.get('service') && this.relier.get('context') === 'web' + ); }, onSuggestSyncDismiss() { diff --git a/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js b/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js new file mode 100644 index 00000000000..ca729700f73 --- /dev/null +++ b/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import BackMixin from './mixins/back-mixin'; +import Cocktail from 'cocktail'; +import DoNotSyncMixin from './mixins/do-not-sync-mixin'; +import FlowEventsMixin from './mixins/flow-events-mixin'; +import FormView from './form'; +import Template from 'templates/would-you-like-to-sync.mustache'; + +const SCREEN_CLASS = 'screen-would-you-like-to-sync'; + +const View = FormView.extend( + { + template: Template, + className: 'would-you-like-to-sync', + + initialize(options = {}) { + // Account data is passed in from sign up flow. + this._account = this.user.initAccount(this.model.get('account')); + + // to keep the view from knowing too much about the state machine, + // a continuation function is passed in that should be called + // when submit has completed. + this.onSubmitComplete = this.model.get('onSubmitComplete'); + }, + + getAccount() { + return this._account; + }, + + beforeRender() { + // user cannot proceed if they have not initiated a sign up/in. + if (!this.getAccount().get('sessionToken')) { + this.navigate('signup'); + } + }, + + submit() { + return Promise.resolve().then(() => { + const account = this.getAccount(); + + // we replace the current view to avoid various problems with the back button + return this.replaceCurrentPage('choose_what_to_sync', { + account: account, + // choose_what_to_sync screen will call onSubmitComplete + // with an updated account + onSubmitComplete: this.onSubmitComplete, + allowToDisableSync: false, + }); + }); + }, + }, + { + SCREEN_CLASS, + } +); + +Cocktail.mixin(View, BackMixin, FlowEventsMixin, DoNotSyncMixin); + +export default View; diff --git a/packages/fxa-content-server/app/tests/spec/lib/app-start.js b/packages/fxa-content-server/app/tests/spec/lib/app-start.js index 5265b403aa5..bdb35c2c918 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/app-start.js +++ b/packages/fxa-content-server/app/tests/spec/lib/app-start.js @@ -22,7 +22,7 @@ import SameBrowserVerificationModel from 'models/verification/same-browser'; import sinon from 'sinon'; import Storage from 'lib/storage'; import StorageMetrics from 'lib/storage-metrics'; -import SyncRelier from 'models/reliers/sync'; +import BrowserRelier from 'models/reliers/browser'; import Url from 'lib/url'; import User from 'models/user'; import WindowMock from '../../mocks/window'; @@ -400,11 +400,11 @@ describe('lib/app-start', () => { }); }); - it('creates an SyncRelier if Sync', () => { + it('creates an BrowserRelier if Sync', () => { sinon.stub(appStart, '_isServiceSync').callsFake(() => true); appStart.initializeRelier(); - assert.instanceOf(appStart._relier, SyncRelier); + assert.instanceOf(appStart._relier, BrowserRelier); }); it('creates an OAuthRelier if in the OAuth flow, even if service=sync is specified', () => { diff --git a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js index f4537e26c24..f11b6139f8c 100644 --- a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js +++ b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js @@ -9,11 +9,13 @@ import FxSyncChannelAuthenticationBroker from 'models/auth_brokers/fx-sync-chann import NullChannel from 'lib/channels/null'; import sinon from 'sinon'; import User from 'models/user'; +import Relier from 'models/reliers/relier'; import WindowMock from '../../../mocks/window'; describe('models/auth_brokers/fx-sync-channel', () => { let account; let broker; + let relier; let channelMock; let user; let windowMock; @@ -35,6 +37,7 @@ describe('models/auth_brokers/fx-sync-channel', () => { VERIFIED: 'verified', }, window: windowMock, + relier, }, options ) @@ -47,6 +50,7 @@ describe('models/auth_brokers/fx-sync-channel', () => { channelMock.send = sinon.spy(() => { return Promise.resolve(); }); + relier = new Relier(); user = new User(); account = user.initAccount({ @@ -440,4 +444,59 @@ describe('models/auth_brokers/fx-sync-channel', () => { assert.equal(broker.getCommand('LOGIN'), 'login'); }); }); + + describe('_getLoginData', () => { + it('formats the login data', () => { + const loginData = broker._getLoginData(account); + assert.deepEqual(loginData, { + declinedSyncEngines: ['addons'], + email: 'testuser@testuser.com', + keyFetchToken: 'key-fetch-token', + offeredSyncEngines: ['tabs', 'addons', 'creditcards', 'addresses'], + sessionToken: 'session-token', + uid: 'uid', + unwrapBKey: 'unwrap-b-key', + verified: false, + verifiedCanLinkAccount: false, + }); + }); + + it('formats the login data for multi-service', () => { + broker.relier.set('multiService', true); + const loginData = broker._getLoginData(account); + + assert.deepEqual(loginData, { + email: 'testuser@testuser.com', + keyFetchToken: 'key-fetch-token', + sessionToken: 'session-token', + uid: 'uid', + unwrapBKey: 'unwrap-b-key', + verified: false, + services: { + sync: { + offeredEngines: ['tabs', 'addons', 'creditcards', 'addresses'], + declinedEngines: ['addons'], + }, + }, + verifiedCanLinkAccount: false, + }); + }); + + it('formats the login data for multi-service that did not opt-in to sync', () => { + broker.relier.set('multiService', true); + broker.relier.set('doNotSync', true); + const loginData = broker._getLoginData(account); + + assert.deepEqual(loginData, { + email: 'testuser@testuser.com', + keyFetchToken: 'key-fetch-token', + sessionToken: 'session-token', + uid: 'uid', + unwrapBKey: 'unwrap-b-key', + verified: false, + services: {}, + verifiedCanLinkAccount: false, + }); + }); + }); }); diff --git a/packages/fxa-content-server/app/tests/spec/models/reliers/sync.js b/packages/fxa-content-server/app/tests/spec/models/reliers/browser.js similarity index 93% rename from packages/fxa-content-server/app/tests/spec/models/reliers/sync.js rename to packages/fxa-content-server/app/tests/spec/models/reliers/browser.js index 78f2ee97c78..379b7ac129b 100644 --- a/packages/fxa-content-server/app/tests/spec/models/reliers/sync.js +++ b/packages/fxa-content-server/app/tests/spec/models/reliers/browser.js @@ -4,7 +4,7 @@ import { assert } from 'chai'; import AuthErrors from 'lib/auth-errors'; -import Relier from 'models/reliers/sync'; +import Relier from 'models/reliers/browser'; import TestHelpers from '../../../lib/helpers'; import Translator from 'lib/translator'; import WindowMock from '../../../mocks/window'; @@ -14,7 +14,7 @@ const CONTEXT = 'fx_desktop_v3'; const COUNTRY = 'RO'; const SYNC_SERVICE = 'sync'; -describe('models/reliers/sync', () => { +describe('models/reliers/browser', () => { let err; let relier; let translator; @@ -41,6 +41,10 @@ describe('models/reliers/sync', () => { ); }); + it('has a proper relier name', () => { + assert.equal(relier.get('name'), 'browser'); + }); + describe('fetch', () => { it('populates model from the search parameters', () => { windowMock.location.search = TestHelpers.toSearchString({ @@ -269,6 +273,16 @@ describe('models/reliers/sync', () => { assert.equal(relier.get('serviceName'), 'Firefox Sync'); }); }); + + it('translates no `service` to generic Firefox `serviceName`', () => { + windowMock.location.search = TestHelpers.toSearchString({ + context: CONTEXT, + }); + + return relier.fetch().then(() => { + assert.equal(relier.get('serviceName'), 'Firefox'); + }); + }); }); describe('isSync', () => { diff --git a/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js b/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js index b86994fb4f9..92b620f4246 100644 --- a/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js +++ b/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js @@ -7,9 +7,9 @@ import Account from 'models/account'; import { assert } from 'chai'; import Backbone from 'backbone'; import Broker from 'models/auth_brokers/base'; -import { CHOOSE_WHAT_TO_SYNC } from '../../../../tests/functional/lib/selectors'; import Metrics from 'lib/metrics'; import Notifier from 'lib/channels/notifier'; +import Relier from 'models/reliers/base'; import sinon from 'sinon'; import SentryMetrics from 'lib/sentry'; import SessionVerificationPoll from 'models/polls/session-verification'; @@ -19,8 +19,6 @@ import User from 'models/user'; import View from 'views/choose_what_to_sync'; import WindowMock from '../../mocks/window'; -const Selectors = CHOOSE_WHAT_TO_SYNC; - describe('views/choose_what_to_sync', () => { let account; let broker; @@ -29,6 +27,7 @@ describe('views/choose_what_to_sync', () => { let metrics; let notifier; let onSubmitComplete; + let relier; let sessionVerificationPoll; let syncEngines; let user; @@ -55,6 +54,7 @@ describe('views/choose_what_to_sync', () => { broker = new Broker({ chooseWhatToSyncWebV1Engines: syncEngines, }); + relier = new Relier(); sinon .stub(broker, 'persistVerificationData') .callsFake(() => Promise.resolve()); @@ -95,12 +95,13 @@ describe('views/choose_what_to_sync', () => { view = null; }); - function initView(options = {}) { + function initView() { view = new View({ broker, metrics, model, notifier, + relier, sessionVerificationPoll, user, viewName: 'choose-what-to-sync', @@ -128,30 +129,62 @@ describe('views/choose_what_to_sync', () => { assert.isTrue(view.navigate.calledWith('signup')); }); }); + }); - it('renders email info, adds SCREEN_CLASS to body', () => { - return initView().then(() => { - assert.include(view.$('.success-email-created').text(), email); + describe('pollVerification', () => { + it('polls', () => { + model.set('pollVerification', true); + return initView() + .then(() => { + sinon.spy(view, 'waitForSessionVerification'); + return view.afterVisible(); + }) + .then(() => { + assert.isTrue(view.waitForSessionVerification.calledOnce); + }); + }); - assert.isTrue($('body').hasClass(View.SCREEN_CLASS)); + it('does not poll', () => { + model.set('pollVerification', false); + return initView() + .then(() => { + sinon.spy(view, 'waitForSessionVerification'); + return view.afterVisible(); + }) + .then(() => { + assert.isFalse(view.waitForSessionVerification.calledOnce); + }); + }); + }); - const $rowEls = view.$('.choose-what-to-sync-row'); - assert.lengthOf($rowEls, DISPLAYED_ENGINE_IDS.length); + describe('showDoNotSyncButton', () => { + it('shows the do not sync button', () => { + model.set('allowToDisableSync', true); + return initView() + .then(() => { + return view.afterVisible(); + }) + .then(() => { + assert.lengthOf(view.$('#do-not-sync-device'), 1); + }); + }); - assert.lengthOf(view.$(Selectors.NEWSLETTERS_HEADER), 0); - assert.lengthOf( - view.$(Selectors.NEWSLETTERS.FIREFOX_ACCOUNTS_JOURNEY), - 0 - ); - assert.lengthOf(view.$(Selectors.NEWSLETTERS.HEALTHY_INTERNET), 0); - assert.lengthOf(view.$(Selectors.NEWSLETTERS.CONSUMER_BETA), 0); - assert.lengthOf(view.$(Selectors.NEWSLETTERS.ONLINE_SAFETY), 0); - }); + it('hides the do not sync button', () => { + model.set('allowToDisableSync', false); + return initView() + .then(() => { + return view.afterVisible(); + }) + .then(() => { + assert.lengthOf(view.$('#do-not-sync-device'), 0); + }); }); }); describe('afterVisible', () => { it('persists verification data, starts poll', () => { + model.set('pollVerification', true); + return initView() .then(() => { sinon.stub(view, 'waitForSessionVerification').callsFake(() => {}); @@ -169,6 +202,8 @@ describe('views/choose_what_to_sync', () => { describe('session verification polling', () => { it('invokes `validateAndSubmit` on verification', () => { + model.set('pollVerification', true); + return initView() .then(() => { sinon.spy(view, 'waitForSessionVerification'); diff --git a/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js b/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js index 4049a0b73cd..42c4b68d7f3 100644 --- a/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js +++ b/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js @@ -12,7 +12,7 @@ import Metrics from 'lib/metrics'; import Notifier from 'lib/channels/notifier'; import Relier from 'models/reliers/relier'; import sinon from 'sinon'; -import SyncRelier from 'models/reliers/sync'; +import BrowserRelier from 'models/reliers/browser'; import TestHelpers from '../../lib/helpers'; import Translator from 'lib/translator'; import User from 'models/user'; @@ -212,7 +212,7 @@ describe('views/complete_sign_up', function() { validUid + '&service=' + validService; - relier = new SyncRelier( + relier = new BrowserRelier( {}, { window: windowMock, @@ -290,7 +290,7 @@ describe('views/complete_sign_up', function() { validService + '&reminder=' + validReminder; - relier = new SyncRelier( + relier = new BrowserRelier( {}, { window: windowMock, diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js new file mode 100644 index 00000000000..55525b6ed69 --- /dev/null +++ b/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { assert } from 'chai'; +import DoNotSyncMixin from 'views/mixins/do-not-sync-mixin'; +import BaseView from 'views/base'; +import Relier from 'models/reliers/base'; +import Broker from 'models/auth_brokers/base'; +import Cocktail from 'cocktail'; +import sinon from 'sinon'; + +class View extends BaseView { + template() { + return 'Do not Sync'; + } + + constructor(options) { + super(options); + this.onSubmitComplete = options.onSubmitComplete; + } +} + +Cocktail.mixin(View, DoNotSyncMixin); + +describe('views/mixins/do-not-sync-mixin', function() { + let broker; + let view; + let relier; + + beforeEach(function() { + relier = new Relier(); + broker = new Broker(); + view = new View({ + relier, + broker, + onSubmitComplete: sinon.spy(), + }); + + return view.render(); + }); + + describe('doNotSync', function() { + it('triggers onSubmitComplete', () => { + return view.doNotSync().then(() => { + assert.isTrue(view.onSubmitComplete.calledOnce); + assert.isTrue(relier.get('doNotSync')); + }); + }); + }); +}); diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js index c93f6061dc1..de351f406c0 100644 --- a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js +++ b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js @@ -9,6 +9,7 @@ import AuthErrors from 'lib/auth-errors'; import Backbone from 'backbone'; import OAuthErrors from 'lib/oauth-errors'; import Relier from 'models/reliers/relier'; +import BrowserRelier from 'models/reliers/browser'; import SignInMixin from 'views/mixins/signin-mixin'; import sinon from 'sinon'; import User from 'models/user'; @@ -114,6 +115,53 @@ describe('views/mixins/signin-mixin', function() { }); }); + describe('offers to Sync', function() { + it('navigates to the correct view', function() { + view.relier = new BrowserRelier(); + sinon.spy(view.relier, 'shouldOfferToSync'); + + return view.signIn(account, 'password').then(() => { + assert.isTrue(view.relier.shouldOfferToSync.calledOnce); + assert.isTrue(view.navigate.calledOnce); + + var args = view.navigate.args[0]; + assert.equal(args[0], 'would_you_like_to_sync'); + assert.deepEqual(args[1].account, account); + assert.isFunction(args[1].onSubmitComplete); + }); + }); + + it('skips if relier does not support it', function() { + sinon.spy(view.relier, 'shouldOfferToSync'); + + return view.signIn(account, 'password').then(() => { + assert.isTrue(view.relier.shouldOfferToSync.calledOnce); + assert.isFalse(view.navigate.calledOnce); + }); + }); + + it('skips if service is sync already', function() { + view.relier = new BrowserRelier(); + view.relier.set('service', 'sync'); + sinon.spy(view.relier, 'shouldOfferToSync'); + + return view.signIn(account, 'password').then(() => { + assert.isTrue(view.relier.shouldOfferToSync.calledOnce); + assert.isFalse(view.navigate.calledOnce); + }); + }); + + it('skips if viewName is force-auth', function() { + view.relier = new BrowserRelier(); + sinon.spy(view.relier, 'shouldOfferToSync'); + + view.viewName = 'force-auth'; + return view.signIn(account, 'password').then(() => { + assert.isFalse(view.navigate.called); + }); + }); + }); + describe('verified account', function() { describe('with `redirectTo` specified', function() { beforeEach(function() { diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js index 4969685fb1c..886ceeb1995 100644 --- a/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js +++ b/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js @@ -8,7 +8,7 @@ import BaseView from 'views/base'; import Cocktail from 'cocktail'; import Constants from 'lib/constants'; import Notifier from 'lib/channels/notifier'; -import Relier from 'models/reliers/sync'; +import Relier from 'models/reliers/browser'; import sinon from 'sinon'; import SyncSuggestionMixin from 'views/mixins/sync-suggestion-mixin'; @@ -45,6 +45,7 @@ describe('views/mixins/sync-suggestion-mixin', () => { describe('sync suggestion', () => { it('displays sync suggestion message if no service', () => { relier.set('service', null); + relier.set('context', 'web'); return view .render() @@ -67,6 +68,8 @@ describe('views/mixins/sync-suggestion-mixin', () => { it('does not have sync auth supported', () => { relier.set('service', null); + relier.set('context', 'web'); + sinon.stub(view, 'isSyncAuthSupported').callsFake(() => false); return view.render().then(() => { const $getStartedEl = view.$('#suggest-sync').find('a'); @@ -79,6 +82,8 @@ describe('views/mixins/sync-suggestion-mixin', () => { it('has sync auth supported on Firefox for Desktop', () => { relier.set('service', null); + relier.set('context', 'web'); + sinon.stub(view, 'isSyncAuthSupported').callsFake(() => true); sinon.stub(view, 'getUserAgent').callsFake(() => { return { @@ -98,6 +103,8 @@ describe('views/mixins/sync-suggestion-mixin', () => { it('has sync auth supported on Firefox for Android', () => { relier.set('service', null); + relier.set('context', 'web'); + sinon.stub(view, 'isSyncAuthSupported').callsFake(() => true); sinon.stub(view, 'getUserAgent').callsFake(() => { return { @@ -117,6 +124,7 @@ describe('views/mixins/sync-suggestion-mixin', () => { it('can be dismissed', () => { relier.set('service', null); + relier.set('context', 'web'); return view.render().then(() => { $('#container').html(view.el); @@ -128,6 +136,7 @@ describe('views/mixins/sync-suggestion-mixin', () => { it('does not display sync suggestion message if there is a relier service', () => { relier.set('service', 'sync'); + relier.set('context', 'web'); return view.render().then(() => { assert.lengthOf(view.$('#suggest-sync'), 0); diff --git a/packages/fxa-content-server/app/tests/spec/views/ready.js b/packages/fxa-content-server/app/tests/spec/views/ready.js index dcbdb7ef196..23aee660cd8 100644 --- a/packages/fxa-content-server/app/tests/spec/views/ready.js +++ b/packages/fxa-content-server/app/tests/spec/views/ready.js @@ -12,7 +12,7 @@ import Notifier from 'lib/channels/notifier'; import OAuthBroker from 'models/auth_brokers/oauth-redirect'; import Session from 'lib/session'; import sinon from 'sinon'; -import SyncRelier from 'models/reliers/sync'; +import BrowserRelier from 'models/reliers/browser'; import View from 'views/ready'; import WindowMock from '../../mocks/window'; @@ -32,7 +32,7 @@ describe('views/ready', function() { windowMock.navigator.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0'; - relier = new SyncRelier({ + relier = new BrowserRelier({ window: windowMock, }); broker = new OAuthBroker({ diff --git a/packages/fxa-content-server/app/tests/spec/views/sign_up.js b/packages/fxa-content-server/app/tests/spec/views/sign_up.js index e4d42155f34..ca100b699d1 100644 --- a/packages/fxa-content-server/app/tests/spec/views/sign_up.js +++ b/packages/fxa-content-server/app/tests/spec/views/sign_up.js @@ -17,7 +17,7 @@ import FxaClient from 'lib/fxa-client'; import Metrics from 'lib/metrics'; import Notifier from 'lib/channels/notifier'; import p from 'lib/promise'; -import Relier from 'models/reliers/sync'; +import Relier from 'models/reliers/browser'; import sinon from 'sinon'; import TestHelpers from '../../lib/helpers'; import Translator from 'lib/translator'; diff --git a/packages/fxa-content-server/app/tests/test_start.js b/packages/fxa-content-server/app/tests/test_start.js index c85e0439fe4..dddf58aa7b6 100644 --- a/packages/fxa-content-server/app/tests/test_start.js +++ b/packages/fxa-content-server/app/tests/test_start.js @@ -120,7 +120,7 @@ require('./spec/models/reliers/oauth'); require('./spec/models/reliers/pairing/authority'); require('./spec/models/reliers/pairing/supplicant'); require('./spec/models/reliers/relier'); -require('./spec/models/reliers/sync'); +require('./spec/models/reliers/browser'); require('./spec/models/resume-token'); require('./spec/models/security-events'); require('./spec/models/support-form'); @@ -172,6 +172,7 @@ require('./spec/views/mixins/cached-credentials-mixin'); require('./spec/views/mixins/connect-another-device-mixin'); require('./spec/views/mixins/coppa-mixin'); require('./spec/views/mixins/disable-form-mixin'); +require('./spec/views/mixins/do-not-sync-mixin'); require('./spec/views/mixins/pairing-graphics-mixin'); require('./spec/views/mixins/email-first-experiment-mixin'); require('./spec/views/mixins/email-opt-in-mixin'); diff --git a/packages/fxa-content-server/server/lib/routes/get-frontend.js b/packages/fxa-content-server/server/lib/routes/get-frontend.js index e386a4820ca..896a00a17bf 100644 --- a/packages/fxa-content-server/server/lib/routes/get-frontend.js +++ b/packages/fxa-content-server/server/lib/routes/get-frontend.js @@ -81,6 +81,7 @@ module.exports = function() { 'verify_email', 'verify_primary_email', 'verify_secondary_email', + 'would_you_like_to_sync', ].join('|'); // prepare for use in a RegExp return { diff --git a/packages/fxa-content-server/tests/functional.js b/packages/fxa-content-server/tests/functional.js index bc49217e7e3..71526bb791f 100644 --- a/packages/fxa-content-server/tests/functional.js +++ b/packages/fxa-content-server/tests/functional.js @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ module.exports = [ + 'tests/functional/fx_browser_relier.js', 'tests/functional/oauth_webchannel.js', 'tests/functional/reset_password.js', 'tests/functional/oauth_require_totp.js', diff --git a/packages/fxa-content-server/tests/functional/fx_browser_relier.js b/packages/fxa-content-server/tests/functional/fx_browser_relier.js new file mode 100644 index 00000000000..b613b6d1889 --- /dev/null +++ b/packages/fxa-content-server/tests/functional/fx_browser_relier.js @@ -0,0 +1,204 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { registerSuite } = intern.getInterface('object'); +const TestHelpers = require('../lib/helpers'); +const FunctionalHelpers = require('./lib/helpers'); +const selectors = require('./lib/selectors'); +const uaStrings = require('./lib/ua-strings'); + +const config = intern._config; +const URL_PARAMS = + 'context=fx_desktop_v3&forceAboutAccounts=true&automatedBrowser=true&action=email'; +const EMAIL_FIRST_URL = `${config.fxaContentRoot}?${URL_PARAMS}`; +const FIREFOX_CLIENT_ID = '5882386c6d801776'; +const CAPABILITIES = { + multiService: true, + pairing: false, + engines: ['history'], +}; + +let email; +const PASSWORD = 'passwordvx2'; + +const { + clearBrowserState, + click, + createUser, + type, + closeCurrentWindow, + openPage, + openVerificationLinkInNewTab, + switchToWindow, + testIsBrowserNotified, +} = FunctionalHelpers; + +registerSuite('Firefox Desktop non-sync', { + beforeEach: function() { + email = TestHelpers.createEmail(); + return this.remote.then(clearBrowserState()); + }, + + afterEach: function() { + return this.remote.then(clearBrowserState()); + }, + tests: { + 'signup with no service - do not sync': function() { + return ( + this.remote + .then( + openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, { + query: { + forceUA: uaStrings['desktop_firefox_57'], + }, + webChannelResponses: { + 'fxaccounts:can_link_account': { ok: true }, + 'fxaccounts:fxa_status': { + signedInUser: null, + clientId: FIREFOX_CLIENT_ID, + capabilities: CAPABILITIES, + }, + }, + }) + ) + .then(type(selectors.ENTER_EMAIL.EMAIL, email)) + .then(click(selectors.ENTER_EMAIL.SUBMIT)) + .then(testIsBrowserNotified('fxaccounts:can_link_account')) + + .then(type(selectors.SIGNUP_PASSWORD.PASSWORD, PASSWORD)) + .then(type(selectors.SIGNUP_PASSWORD.VPASSWORD, PASSWORD)) + .then(type(selectors.SIGNUP_PASSWORD.AGE, 21)) + .then( + click( + selectors.SIGNUP_PASSWORD.SUBMIT, + selectors.CHOOSE_WHAT_TO_SYNC.HEADER + ) + ) + // verify the account + .then(openVerificationLinkInNewTab(email, 0)) + .then(switchToWindow(1)) + // switch back to the original window, choose to "do not sync". + .then(closeCurrentWindow()) + .then(click(selectors.CHOOSE_WHAT_TO_SYNC.DO_NOT_SYNC)) + .then(testIsBrowserNotified('fxaccounts:login')) + ); + }, + 'signup with no service - sync': function() { + return ( + this.remote + .then( + openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, { + query: { + forceUA: uaStrings['desktop_firefox_57'], + }, + webChannelResponses: { + 'fxaccounts:can_link_account': { ok: true }, + 'fxaccounts:fxa_status': { + signedInUser: null, + clientId: FIREFOX_CLIENT_ID, + capabilities: CAPABILITIES, + }, + }, + }) + ) + .then(type(selectors.ENTER_EMAIL.EMAIL, email)) + .then(click(selectors.ENTER_EMAIL.SUBMIT)) + .then(testIsBrowserNotified('fxaccounts:can_link_account')) + + .then(type(selectors.SIGNUP_PASSWORD.PASSWORD, PASSWORD)) + .then(type(selectors.SIGNUP_PASSWORD.VPASSWORD, PASSWORD)) + .then(type(selectors.SIGNUP_PASSWORD.AGE, 21)) + .then( + click( + selectors.SIGNUP_PASSWORD.SUBMIT, + selectors.CHOOSE_WHAT_TO_SYNC.HEADER + ) + ) + // verify the account + .then(openVerificationLinkInNewTab(email, 0)) + .then(switchToWindow(1)) + // switch back to the original window, choose to "do not sync". + .then(closeCurrentWindow()) + .then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT)) + .then(testIsBrowserNotified('fxaccounts:login')) + ); + }, + 'signin with no service - do not sync': function() { + return this.remote + .then(createUser(email, PASSWORD, { preVerified: true })) + .then( + openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, { + query: { + forceUA: uaStrings['desktop_firefox_57'], + }, + webChannelResponses: { + 'fxaccounts:can_link_account': { ok: true }, + 'fxaccounts:fxa_status': { + signedInUser: null, + clientId: FIREFOX_CLIENT_ID, + capabilities: CAPABILITIES, + }, + }, + }) + ) + .then(type(selectors.ENTER_EMAIL.EMAIL, email)) + .then( + click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER) + ) + .then(testIsBrowserNotified('fxaccounts:can_link_account')) + + .then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD)) + .then( + click( + selectors.SIGNIN_PASSWORD.SUBMIT, + selectors.WOULD_YOU_LIKE_SYNC.HEADER + ) + ) + .then(click(selectors.WOULD_YOU_LIKE_SYNC.DO_NOT_SYNC)) + .then(testIsBrowserNotified('fxaccounts:login')); + }, + 'signin with no service - sync': function() { + return this.remote + .then(createUser(email, PASSWORD, { preVerified: true })) + .then( + openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, { + query: { + forceUA: uaStrings['desktop_firefox_57'], + }, + webChannelResponses: { + 'fxaccounts:can_link_account': { ok: true }, + 'fxaccounts:fxa_status': { + signedInUser: null, + clientId: FIREFOX_CLIENT_ID, + capabilities: CAPABILITIES, + }, + }, + }) + ) + .then(type(selectors.ENTER_EMAIL.EMAIL, email)) + .then( + click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER) + ) + .then(testIsBrowserNotified('fxaccounts:can_link_account')) + + .then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD)) + .then( + click( + selectors.SIGNIN_PASSWORD.SUBMIT, + selectors.WOULD_YOU_LIKE_SYNC.HEADER + ) + ) + .then( + click( + selectors.WOULD_YOU_LIKE_SYNC.SUBMIT, + selectors.CHOOSE_WHAT_TO_SYNC.HEADER + ) + ) + .then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT)) + .then(testIsBrowserNotified('fxaccounts:login')); + }, + }, +}); diff --git a/packages/fxa-content-server/tests/functional/lib/selectors.js b/packages/fxa-content-server/tests/functional/lib/selectors.js index c900f4251ff..f04ba476db4 100644 --- a/packages/fxa-content-server/tests/functional/lib/selectors.js +++ b/packages/fxa-content-server/tests/functional/lib/selectors.js @@ -49,7 +49,13 @@ module.exports = { SUBMIT: '#change-password button[type="submit"]', TOOLTIP: '.tooltip', }, + WOULD_YOU_LIKE_SYNC: { + HEADER: '#would-you-like-to-sync', + SUBMIT: 'button[type=submit]', + DO_NOT_SYNC: '#do-not-sync-device', + }, CHOOSE_WHAT_TO_SYNC: { + DO_NOT_SYNC: '#do-not-sync-device', ENGINE_ADDRESSES: '#sync-engine-addresses', ENGINE_BOOKMARKS: '#sync-engine-bookmarks', ENGINE_CREDIT_CARDS: '#sync-engine-creditcards', diff --git a/packages/fxa-content-server/tests/functional/sync_v3_email_first.js b/packages/fxa-content-server/tests/functional/sync_v3_email_first.js index b314e3b4a9c..5a115c1c718 100644 --- a/packages/fxa-content-server/tests/functional/sync_v3_email_first.js +++ b/packages/fxa-content-server/tests/functional/sync_v3_email_first.js @@ -248,7 +248,7 @@ registerSuite('Firefox Desktop Sync v3 email first', { .then(testIsBrowserNotified('fxaccounts:can_link_account')); }, - 'signin verified ': function() { + 'signin verified': function() { return ( this.remote .then(createUser(email, PASSWORD, { preVerified: true }))