fixes #5262
This commit is contained in:
Shane Tomlinson 2017-08-24 13:49:33 +01:00
Родитель 65588f6f20
Коммит ef9eb398f2
31 изменённых файлов: 775 добавлений и 185 удалений

Просмотреть файл

@ -0,0 +1,52 @@
/* 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/. */
define((require, exports, module) => {
'use strict';
const BaseGroupingRule = require('./base');
function isEmailForcedIntoTreatment (email) {
return /@softvision\.(com|ro)$/.test(email) ||
/@mozilla\.(com|org)$/.test(email);
}
module.exports = class CadOnSigninGroupingRule extends BaseGroupingRule {
constructor () {
super();
this.name = 'cadOnSignin';
}
/**
* Use `subject` data to decide if the user should see CAD on signin.
*
* @param {Object} subject data used to decide
* @returns {Any}
*/
choose (subject) {
if (! this._areSubjectPrereqsMet(subject)) {
return false;
}
if (isEmailForcedIntoTreatment(subject.account.get('email'))) {
return 'treatment';
}
return false;
}
/**
* Are the subject pre-requisites met?
*
* @param {Object} subject
* @returns {Boolean}
* @private
*/
_areSubjectPrereqsMet (subject) {
return subject &&
subject.account &&
subject.account.get('email');
}
};
});

Просмотреть файл

@ -13,6 +13,7 @@ define((require, exports, module) => {
const ExperimentGroupingRules = [
require('./communication-prefs'),
require('./connect-another-device-on-signin'),
require('./disabled-button-state'),
require('./email-first'),
require('./is-sampled-user'),

Просмотреть файл

@ -20,7 +20,7 @@ define((require, exports, module) => {
* @returns {Any}
*/
choose (subject) {
const EXPERIMENTS = ['disabledButtonState', 'signupPasswordConfirm', 'emailFirst'];
const EXPERIMENTS = ['disabledButtonState', 'signupPasswordConfirm', 'emailFirst', 'cadOnSignin'];
if (! subject || ! subject.uniqueUserId) {
return false;
}

Просмотреть файл

@ -30,6 +30,8 @@ define(function (require, exports, module) {
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
browserTransitionsAfterEmailVerification: true,
cadAfterSignInConfirmationPoll: true,
cadAfterSignUpConfirmationPoll: true,
chooseWhatToSyncCheckbox: false,
chooseWhatToSyncWebV1: true,
openWebmailButtonVisible: true

Просмотреть файл

@ -27,6 +27,7 @@ define(function (require, exports, module) {
},
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
cadAfterSignInConfirmationPoll: true,
cadAfterSignUpConfirmationPoll: true,
chooseWhatToSyncCheckbox: true,
chooseWhatToSyncWebV1: false,

Просмотреть файл

@ -14,16 +14,20 @@ define(function (require, exports, module) {
const _ = require('underscore');
const BaseAuthenticationBroker = require('models/auth_brokers/base');
const ConnectAnotherDeviceBehavior = require('views/behaviors/connect-another-device');
const ConnectAnotherDeviceOnSigninBehavior = require('views/behaviors/connect-another-device-on-signin');
const SyncEngines = require('models/sync-engines');
const proto = BaseAuthenticationBroker.prototype;
module.exports = BaseAuthenticationBroker.extend({
defaultBehaviors: _.extend({}, proto.defaultBehaviors, {
afterCompleteSignIn: new ConnectAnotherDeviceOnSigninBehavior(proto.defaultBehaviors.afterCompleteSignIn),
afterCompleteSignUp: new ConnectAnotherDeviceBehavior(proto.defaultBehaviors.afterCompleteSignUp)
}),
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
// Can CAD be displayed after the signin confirmation poll?
cadAfterSignInConfirmationPoll: false,
// Can CAD be displayed after the signup confirmation poll?
cadAfterSignUpConfirmationPoll: false
}),
@ -69,13 +73,35 @@ define(function (require, exports, module) {
}
},
afterSignInConfirmationPoll (account) {
return proto.afterSignInConfirmationPoll.call(this, account)
.then((defaultBehavior) => {
if (this.hasCapability('cadAfterSignInConfirmationPoll')) {
// This is a hack to allow us to differentiate between users
// who see CAD in the signin and verification tabs. CAD
// was added to the verification tab first, view names and view
// events are all unprefixed. In the signin tab, we force add
// the `signin` view name prefix so that events that contain
// viewNames have `signin` view name prefix.
//
// e.g.:
// screen.sms <- view the sms screen in the verification tab.
// screen.signin.sms <- view the sms screen in the signin tab.
this._metrics.setViewNamePrefix('signin');
return new ConnectAnotherDeviceOnSigninBehavior(defaultBehavior);
}
return defaultBehavior;
});
},
afterSignUpConfirmationPoll (account) {
return proto.afterSignUpConfirmationPoll.call(this, account)
.then((defaultBehavior) => {
if (this.hasCapability('cadAfterSignUpConfirmationPoll')) {
// This is a hack to allow us to differentiate between users
// who see CAD in the signup and verification tabs. CAD
// was added to the verifiation tab first, view names and view
// was added to the verification tab first, view names and view
// events are all unprefixed. In the signup tab, we force add
// the `signup` view name prefix so that events that contain
// viewNames have `signup` view name prefix.

Просмотреть файл

@ -4,13 +4,18 @@
<div class="success success-authenticated visible">{{#t}}This Firefox is connected{{/t}}</div>
{{/isSignedIn}}
{{^isSignedIn}}
<div class="success success-not-authenticated visible">{{#t}}Email verified{{/t}}</div>
{{#isSignUp}}
<div class="success success-not-authenticated visible">{{#t}}Email verified{{/t}}</div>
{{/isSignUp}}
{{#isSignIn}}
<div class="success success-not-authenticated visible">{{#t}}Signin confirmed{{/t}}</div>
{{/isSignIn}}
{{/isSignedIn}}
<section>
<div class="graphic graphic-connect-another-device">{{#t}}Success{{/t}}</div>
{{#canSignIn}}
<p>
<p class="instructions">
{{#t}}Sign in to this Firefox to complete set-up{{/t}}
</p>
@ -25,36 +30,45 @@
<!-- User verified in Fx for Android - they are using an old Fennec
or are already signed in. Ignore old browsers, assume already signed in.
Encourage installation on another device. -->
<p id="connect-other-firefox-from-android">
<p id="connect-other-firefox-from-android" class="instructions">
{{#isSignIn}}
{{#t}}Still adding devices?{{/t}}
{{/isSignIn}}
{{#t}}Sign in to Firefox on another device to complete set-up{{/t}}
</p>
{{/isFirefoxAndroid}}
{{#isFirefoxDesktop}}
<p id="install-mobile-firefox-desktop">
<p id="install-mobile-firefox-desktop" class="instructions">
{{#isSignIn}}
{{#t}}Still adding devices?{{/t}}
{{/isSignIn}}
{{#t}}Sign in to Firefox on another device to complete set-up{{/t}}
</p>
{{/isFirefoxDesktop}}
{{#isFirefoxIos}}
<!-- user verifies in Fx for iOS, assume they are not signed in -->
<p id="signin-fxios">
<p id="signin-fxios" class="instructions">
{{#t}}Open settings and select Sign in to Firefox to complete set-up{{/t}}
</p>
{{/isFirefoxIos}}
{{#isOtherAndroid}}
<!-- Another android browser, encourage Fx for Android installation -->
<p id="install-mobile-firefox-android">
<p id="install-mobile-firefox-android" class="instructions">
{{#t}}Sign in to Firefox for Android to complete set-up{{/t}}
</p>
{{/isOtherAndroid}}
{{#isOtherIos}}
<!-- Safari or Chrome for iOS, encourage installation of Fx -->
<p id="install-mobile-firefox-ios">
<p id="install-mobile-firefox-ios" class="instructions">
{{#t}}Sign in to Firefox for iOS to complete set-up{{/t}}
</p>
{{/isOtherIos}}
{{#isOther}}
<!-- probably some desktop browser -->
<p id="install-mobile-firefox-other">
<p id="install-mobile-firefox-other" class="instructions">
{{#isSignIn}}
{{#t}}Still adding devices?{{/t}}
{{/isSignIn}}
{{#t}}Sign in to Firefox on another device to complete set-up{{/t}}
</p>
{{/isOther}}
@ -64,7 +78,7 @@
{{/isFirefoxIos}}
{{/canSignIn}}
<div class="links">
<a data-flow-event="link.why" href="/connect_another_device/why">{{#t}}Why is this required?{{/t}}</a>
<a data-flow-event="link.why" href="/connect_another_device/why">{{#t}}Why is more than one device required?{{/t}}</a>
</div>
<aside class="child-view"></aside>
</section>

Просмотреть файл

@ -6,7 +6,12 @@
<section>
<div class="graphic graphic-connect-another-device">{{#t}}Success{{/t}}</div>
<p>{{#t}}Send Firefox directly to your smartphone and sign in to complete set-up{{/t}}</p>
<p class="instructions">
{{#isSignIn}}
{{#t}}Still adding devices?{{/t}}
{{/isSignIn}}
{{#t}}Send Firefox directly to your smartphone and sign in to complete set-up{{/t}}
</p>
<form novalidate>
<div class="input-row sms-row">
@ -20,7 +25,7 @@
{{#unsafeTranslate}}SMS service only available in certain countries. SMS &amp; data rates may apply. The intended recipient of the email or SMS must have consented. <a %(escapedLearnMoreAttributes)s>Learn more</a>{{/unsafeTranslate}}
</div>
<div class="links">
<a href="/sms/why" data-flow-event="link.why" class="left">{{#t}}Why is this required?{{/t}}</a>
<a href="/sms/why" data-flow-event="link.why" class="left">{{#t}}Why is more than one device required?{{/t}}</a>
<a id="maybe-later" href="/connect_another_device" data-flow-event="link.maybe_later" class="right">{{#t}}Maybe later{{/t}}</a>
</div>
<aside class="child-view"></aside>

Просмотреть файл

@ -810,6 +810,7 @@ define(function (require, exports, module) {
server: true,
url
});
this._hasNavigated = true;
},
/**
@ -822,6 +823,15 @@ define(function (require, exports, module) {
this.navigate(url, nextViewData, { replace: true, trigger: true });
},
/**
* Has the view already navigated?
*
* @returns {Boolean}
*/
hasNavigated () {
return !! this._hasNavigated;
},
/**
* Focus the element with the [autofocus] attribute, if not a touch device.
* Focusing an element on a touch device causes the virtual keyboard to

Просмотреть файл

@ -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/. */
/**
* A behavior that sends eligible users to the appropriate
* connect-another-device screen. If ineligible, fallback
* to `defaultBehavior`.
*
* Should only be used for signin flows, a side effect
* is to create/initialize CAD on signin experiments.
*
* Requires the view to mixin the ConnectAnotherDeviceMixin
*/
define((require, exports, module) => {
'use strict';
const p = require('lib/promise');
/**
* Create a ConnectAnotherDeviceOnSignin behavior.
*
* @param {Object} defaultBehavior - behavior to invoke if ineligible
* for ConnectAnotherDevice
* @returns {Function} behavior
*/
module.exports = function (defaultBehavior) {
const behavior = function (view, account) {
return p().then(() => {
if (view.isEligibleForConnectAnotherDeviceOnSignin(account)) {
return view.navigateToConnectAnotherDeviceOnSigninScreen(account);
}
})
.then(() => {
// if the user is not eligible for CAD, or if the .navigateToConnect*
// function did not navigate, then return the default behavior.
if (view.hasNavigated()) {
// Cause the invokeBrokerMethod chain to stop, the screen
// has already redirected.
return p.defer().promise;
}
return defaultBehavior;
});
};
behavior.type = 'connect-another-device-on-signin';
return behavior;
};
});

Просмотреть файл

@ -7,6 +7,8 @@
* connect-another-device screen. If ineligible, fallback
* to `defaultBehavior`.
*
* Do not use for a signin flow, instead use "connect-another-device-on-signin".
*
* Requires the view to mixin the ConnectAnotherDeviceMixin
*/
@ -24,14 +26,21 @@ define((require, exports, module) => {
*/
module.exports = function (defaultBehavior) {
const behavior = function (view, account) {
if (view.isEligibleForConnectAnotherDevice(account)) {
view.navigateToConnectAnotherDeviceScreen(account);
// Cause the invokeBrokerMethod chain to stop, the screen
// has already redirected.
return p.defer().promise;
}
return defaultBehavior;
return p().then(() => {
if (view.isEligibleForConnectAnotherDevice(account)) {
return view.navigateToConnectAnotherDeviceScreen(account);
}
})
.then(() => {
// if the user is not eligible for CAD, or if the .navigateToConnect*
// function did not navigate, then return the default behavior.
if (view.hasNavigated()) {
// Cause the invokeBrokerMethod chain to stop, the screen
// has already redirected.
return p.defer().promise;
}
return defaultBehavior;
});
};
behavior.type = 'connect-another-device';

Просмотреть файл

@ -21,6 +21,7 @@ define(function (require, exports, module) {
const SyncAuthMixin = require('views/mixins/sync-auth-mixin');
const Template = require('stache!templates/connect_another_device');
const UserAgentMixin = require('lib/user-agent-mixin');
const VerificationReasonMixin = require('views/mixins/verification-reason-mixin');
class ConnectAnotherDeviceView extends FormView {
initialize (options = {}) {
@ -137,6 +138,8 @@ define(function (require, exports, module) {
const isOtherAndroid = isAndroid && ! isFirefoxAndroid;
const isOtherIos = isIos && ! isFirefoxIos;
const isOther = ! isAndroid && ! isIos && ! isFirefoxDesktop;
const isSignIn = this.isSignIn();
const isSignUp = this.isSignUp();
context.set({
canSignIn,
@ -148,6 +151,8 @@ define(function (require, exports, module) {
isOther,
isOtherAndroid,
isOtherIos,
isSignIn,
isSignUp,
isSignedIn
});
}
@ -203,7 +208,8 @@ define(function (require, exports, module) {
service: SYNC_SERVICE
}),
SyncAuthMixin,
UserAgentMixin
UserAgentMixin,
VerificationReasonMixin
);
module.exports = ConnectAnotherDeviceView;

Просмотреть файл

@ -35,7 +35,6 @@ define((require, exports, module) => {
const REASON_NOT_IN_EXPERIMENT = 'sms.ineligible.not_in_experiment';
const REASON_NO_SESSION = 'sms.ineligible.no_session';
const REASON_OTHER_USER_SIGNED_IN = 'sms.ineligible.other_user_signed_in';
const REASON_SIGNIN = 'sms.ineligible.signin';
const REASON_UNSUPPORTED_COUNTRY = 'sms.ineligible.unsupported_country';
const REASON_XHR_ERROR = 'sms.ineligible.xhr_error';
@ -46,6 +45,45 @@ define((require, exports, module) => {
VerificationReasonMixin
],
/**
* Is the user eligible for the CAD on signin?
*
* @param {any} account
* @returns {Booelean}
*/
isEligibleForConnectAnotherDeviceOnSignin (account) {
const isEligibleForCadOnSignin = !! this.getExperimentGroup('cadOnSignin', { account });
return this.isSignIn() &&
this.isEligibleForConnectAnotherDevice(account) &&
isEligibleForCadOnSignin;
},
navigateToConnectAnotherDeviceOnSigninScreen (account) {
if (! this.isEligibleForConnectAnotherDeviceOnSignin(account)) {
// this shouldn't happen IRL.
return p.reject(new Error('navigateToConnectAnotherDeviceOnSigninScreen can only be called if user is eligible to connect another device'));
}
return p().then(() => {
// Initialize the flow metrics so any flow events are logged.
// The flow-events-mixin, even if it were mixed in, does this in
// `afterRender` whereas this method can be called in `beforeRender`
this.notifier.trigger('flow.initialize');
const group = this.getExperimentGroup('cadOnSignin', { account });
// Note, the cadOnSignin prefix is to help us measure in DataDog.
// Metrics sent to DataDog can have one or more exp_group tags,
// the prefix allows us to differentiate between results from
// the `sendSms` experiment which uses the same group names.
this.createExperiment('sendSms', `cadOnSignin.${group}`);
if (group === 'treatment') {
return this.navigateToConnectAnotherDeviceScreen(account);
}
});
},
/**
* Is `account` eligible for connect another device?
*
@ -53,12 +91,9 @@ define((require, exports, module) => {
* @returns {Boolean}
*/
isEligibleForConnectAnotherDevice (account) {
// Only show to users who are signing up, until we have better text for
// users who are signing in.
return this.isSignUp() &&
// If a user is already signed in to Sync which is different to the
// user that just verified, show them the old "Account verified!" screen.
! this.user.isAnotherAccountSignedIn(account);
// If a user is already signed in to Sync which is different to the
// user that just verified, show them the old "Account verified!" screen.
return ! this.user.isAnotherAccountSignedIn(account);
},
/**
@ -83,6 +118,7 @@ define((require, exports, module) => {
return this._isEligibleForSms(account)
.then(({ ok, country }) => {
const type = this.model.get('type');
if (ok) {
// User is eligible for SMS experiment, now bucket
// users into treatment and control groups.
@ -91,13 +127,13 @@ define((require, exports, module) => {
if (group === 'control') {
this.logFlowEvent(REASON_CONTROL_GROUP);
this.navigate('connect_another_device', { account });
this.navigate('connect_another_device', { account, type });
} else {
// all non-control groups go to the sms page.
this.navigate('sms', { account, country });
this.navigate('sms', { account, country, type });
}
} else {
this.navigate('connect_another_device', { account });
this.navigate('connect_another_device', { account, type });
}
});
},
@ -134,9 +170,7 @@ define((require, exports, module) => {
_areSmsRequirementsMet (account) {
let reason;
if (! this.isSignUp()) {
reason = REASON_SIGNIN;
} else if (this.getUserAgent().isAndroid()) {
if (this.getUserAgent().isAndroid()) {
// If already on a mobile device, doesn't make sense to send an SMS.
reason = REASON_ANDROID;
} else if (this.getUserAgent().isIos()) {

Просмотреть файл

@ -24,6 +24,7 @@ define(function (require, exports, module) {
const SmsErrors = require('lib/sms-errors');
const SmsMixin = require('views/mixins/sms-mixin');
const Template = require('stache!templates/sms_send');
const VerificationReasonMixin = require('views/mixins/verification-reason-mixin');
class SmsSendView extends FormView {
initialize (options) {
@ -61,9 +62,12 @@ define(function (require, exports, module) {
phoneNumber = prefix;
}
const isSignIn = this.isSignIn();
context.set({
country,
escapedLearnMoreAttributes,
isSignIn,
phoneNumber,
});
}
@ -178,7 +182,8 @@ define(function (require, exports, module) {
service: SYNC_SERVICE
}),
PulseGraphicMixin,
SmsMixin
SmsMixin,
VerificationReasonMixin
);
module.exports = SmsSendView;

Просмотреть файл

@ -0,0 +1,44 @@
/* 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/. */
define(function (require, exports, module) {
'use strict';
const { assert } = require('chai');
const Account = require('models/account');
const Experiment = require('lib/experiments/grouping-rules/connect-another-device-on-signin');
describe('lib/experiments/grouping-rules/connect-another-device-on-signin', () => {
let account;
let experiment;
before(() => {
account = new Account();
experiment = new Experiment();
});
describe('choose', () => {
it('returns false if no subject, or subject.account, or subject.account.email', () => {
assert.isFalse(experiment.choose());
assert.isFalse(experiment.choose({}));
assert.isFalse(experiment.choose({ account: new Account() }));
});
it('returns true for test email addresses', () => {
account.set('email', 'testuser@testuser.com');
assert.isFalse(experiment.choose({ account }));
account.set('email', 'testuser@softvision.com');
assert.equal(experiment.choose({ account }), 'treatment');
account.set('email', 'testuser@softvision.ro');
assert.equal(experiment.choose({ account }), 'treatment');
account.set('email', 'testuser@mozilla.com');
assert.equal(experiment.choose({ account }), 'treatment');
account.set('email', 'testuser@mozilla.org');
assert.equal(experiment.choose({ account }), 'treatment');
});
});
});
});

Просмотреть файл

@ -24,15 +24,6 @@ define(function (require, exports, module) {
assert.isFalse(experiment.choose({}));
});
it('returns experiment if forceExperiment', () => {
assert.equal(experiment.choose({
account,
forceExperiment: 'disabledButtonState',
forceExperimentGroup: 'control',
uniqueUserId: 'user-id'
}), 'disabledButtonState');
});
it('returns chooses some group ', () => {
assert.ok(experiment.choose({
account,

Просмотреть файл

@ -6,6 +6,7 @@ define((require, exports, module) => {
'use strict';
const { assert } = require('chai');
const Account = require('models/account');
const FxSyncAuthenticationBroker = require('models/auth_brokers/fx-sync');
const Metrics = require('lib/metrics');
const p = require('lib/promise');
@ -45,7 +46,7 @@ define((require, exports, module) => {
}
beforeEach(() => {
account = {};
account = new Account();
metrics = new Metrics();
relier = new Relier();
windowMock = new WindowMock();
@ -141,15 +142,50 @@ define((require, exports, module) => {
});
});
describe('broker does not have `cadAfterSignUpConfirmationPoll` capability', () => {
it('resolves to the default behavior', () => {
sinon.spy(metrics, 'setViewNamePrefix');
describe('afterSignInConfirmationPoll', () => {
describe('broker has `cadAfterSignInConfirmationPoll` capability', () => {
it('sets the metrics viewName prefix, resolves to a `ConnectAnotherDeviceBehavior`', () => {
broker.setCapability('cadAfterSignInConfirmationPoll', true);
sinon.spy(metrics, 'setViewNamePrefix');
return broker.afterSignUpConfirmationPoll(account)
return broker.afterSignInConfirmationPoll(account)
.then((behavior) => {
assert.equal(behavior.type, 'connect-another-device-on-signin');
assert.isTrue(metrics.setViewNamePrefix.calledOnce);
assert.isTrue(metrics.setViewNamePrefix.calledWith('signin'));
});
});
});
describe('broker does not have `cadAfterSignInConfirmationPoll` capability', () => {
it('resolves to the default behavior', () => {
sinon.spy(metrics, 'setViewNamePrefix');
return broker.afterSignInConfirmationPoll(account)
.then((behavior) => {
assert.equal(behavior.type, broker.getBehavior('afterSignInConfirmationPoll').type);
assert.isFalse(metrics.setViewNamePrefix.called);
});
});
});
});
describe('afterCompleteSignUp', () => {
it('returns a ConnectAnotherDeviceBehavior', () => {
return broker.afterCompleteSignUp(account)
.then((behavior) => {
assert.equal(behavior.type, broker.getBehavior('afterSignUpConfirmationPoll').type);
assert.equal(behavior.type, 'connect-another-device');
});
});
});
assert.isFalse(metrics.setViewNamePrefix.called);
describe('afterCompleteSignIn', () => {
it('returns a ConnectAnotherDeviceBehavior', () => {
return broker.afterCompleteSignIn(account)
.then((behavior) => {
assert.equal(behavior.type, 'connect-another-device-on-signin');
});
});
});

Просмотреть файл

@ -0,0 +1,71 @@
/* 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/. */
define((require, exports, module) => {
'use strict';
const { assert } = require('chai');
const ConnectAnotherDeviceOnSigninBehavior = require('views/behaviors/connect-another-device-on-signin');
const NullBehavior = require('views/behaviors/null');
const sinon = require('sinon');
describe('views/behaviors/connect-another-device-on-signin', () => {
let account;
let cadOnSigninBehavior;
let defaultBehavior;
before(() => {
account = {};
defaultBehavior = new NullBehavior();
cadOnSigninBehavior = new ConnectAnotherDeviceOnSigninBehavior(defaultBehavior);
});
describe('eligible for CAD', () => {
it('delegates to `view.navigateToConnectAnotherDeviceOnSigninScreen`', () => {
const view = {
hasNavigated: sinon.spy(() => false),
isEligibleForConnectAnotherDeviceOnSignin: sinon.spy(() => true),
isSignIn: sinon.spy(() => true),
navigateToConnectAnotherDeviceOnSigninScreen: sinon.spy()
};
return cadOnSigninBehavior(view, account)
.then((behavior) => {
assert.strictEqual(behavior, defaultBehavior);
assert.isTrue(view.isEligibleForConnectAnotherDeviceOnSignin.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDeviceOnSignin.calledWith(account));
assert.isTrue(view.navigateToConnectAnotherDeviceOnSigninScreen.calledOnce);
assert.isTrue(view.navigateToConnectAnotherDeviceOnSigninScreen.calledWith(account));
assert.isTrue(view.hasNavigated.calledOnce);
});
});
});
describe('ineligible for CAD', () => {
it('invokes the defaultBehavior', () => {
const view = {
hasNavigated: sinon.spy(() => false),
isEligibleForConnectAnotherDeviceOnSignin: sinon.spy(() => false),
isSignIn: sinon.spy(() => true),
navigateToConnectAnotherDeviceOnSigninScreen: sinon.spy()
};
return cadOnSigninBehavior(view, account)
.then((behavior) => {
assert.strictEqual(behavior, defaultBehavior);
assert.isTrue(view.isEligibleForConnectAnotherDeviceOnSignin.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDeviceOnSignin.calledWith(account));
assert.isFalse(view.navigateToConnectAnotherDeviceOnSigninScreen.called);
assert.isTrue(view.hasNavigated.calledOnce);
});
});
});
});
});

Просмотреть файл

@ -24,36 +24,47 @@ define((require, exports, module) => {
describe('eligible for CAD', () => {
it('delegates to `view.navigateToConnectAnotherDeviceScreen`', () => {
const view = {
hasNavigated: sinon.spy(() => false),
isEligibleForConnectAnotherDevice: sinon.spy(() => true),
isSignIn: sinon.spy(() => false),
navigateToConnectAnotherDeviceScreen: sinon.spy()
};
const promise = cadBehavior(view, account);
assert.ok(promise);
assert.isFunction(promise.then);
return cadBehavior(view, account)
.then((behavior) => {
assert.strictEqual(behavior, defaultBehavior);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledWith(account));
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledWith(account));
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledOnce);
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledWith(account));
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledOnce);
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledWith(account));
assert.isTrue(view.hasNavigated.calledOnce);
});
});
});
describe('ineligible for CAD', () => {
it('invokes the defaultBehavior', () => {
const view = {
hasNavigated: sinon.spy(() => false),
isEligibleForConnectAnotherDevice: sinon.spy(() => false),
isSignIn: sinon.spy(() => false),
navigateToConnectAnotherDeviceScreen: sinon.spy()
};
const behavior = cadBehavior(view, account);
return cadBehavior(view, account)
.then((behavior) => {
assert.strictEqual(behavior, defaultBehavior);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledWith(account));
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledOnce);
assert.isTrue(view.isEligibleForConnectAnotherDevice.calledWith(account));
assert.isFalse(view.navigateToConnectAnotherDeviceScreen.called);
assert.strictEqual(behavior, defaultBehavior);
assert.isFalse(view.navigateToConnectAnotherDeviceScreen.called);
assert.isTrue(view.hasNavigated.calledOnce);
});
});
});
});

Просмотреть файл

@ -7,6 +7,7 @@ define(function (require, exports, module) {
const { assert } = require('chai');
const AuthErrors = require('lib/auth-errors');
const Backbone = require('backbone');
const BaseView = require('views/base');
const ConnectAnotherDeviceMixin = require('views/mixins/connect-another-device-mixin');
const Constants = require('lib/constants');
@ -39,17 +40,20 @@ define(function (require, exports, module) {
describe('views/mixins/connect-another-device-mixin', () => {
let account;
let model;
let notifier;
let relier;
let user;
let view;
beforeEach(() => {
model = new Backbone.Model({ type: 'signin' });
notifier = new Notifier();
relier = new Relier();
user = new User();
view = new View({
model,
notifier,
relier,
user
@ -64,64 +68,91 @@ define(function (require, exports, module) {
});
});
describe('isEligibleForConnectAnotherDeviceOnSignin', () => {
it('returns true if signing in, if eligible for CAD, and is part of the experiment', () => {
sinon.stub(view, 'isSignIn').callsFake(() => true);
sinon.stub(view, 'isEligibleForConnectAnotherDevice').callsFake(() => true);
sinon.stub(view, 'getExperimentGroup').callsFake(() => 'treatment');
assert.isTrue(view.isEligibleForConnectAnotherDeviceOnSignin(account));
});
it('returns false if signing up, if eligible for CAD, and is part of the experiment', () => {
sinon.stub(view, 'isSignIn').callsFake(() => false);
sinon.stub(view, 'isEligibleForConnectAnotherDevice').callsFake(() => true);
sinon.stub(view, 'getExperimentGroup').callsFake(() => 'treatment');
assert.isFalse(view.isEligibleForConnectAnotherDeviceOnSignin(account));
});
it('returns false if signing in, not eligible for CAD, and is part of the experiment', () => {
sinon.stub(view, 'isSignIn').callsFake(() => true);
sinon.stub(view, 'isEligibleForConnectAnotherDevice').callsFake(() => false);
sinon.stub(view, 'getExperimentGroup').callsFake(() => 'treatment');
assert.isFalse(view.isEligibleForConnectAnotherDeviceOnSignin(account));
});
it('returns false if signing in, not eligible for CAD, and is part of the experiment', () => {
sinon.stub(view, 'isSignIn').callsFake(() => true);
sinon.stub(view, 'isEligibleForConnectAnotherDevice').callsFake(() => true);
sinon.stub(view, 'getExperimentGroup').callsFake(() => false);
assert.isFalse(view.isEligibleForConnectAnotherDeviceOnSignin(account));
});
});
describe('navigateToConnectAnotherDeviceOnSigninScreen', () => {
beforeEach(() => {
sinon.stub(view, 'isEligibleForConnectAnotherDeviceOnSignin').callsFake(() => true);
sinon.stub(view, 'navigateToConnectAnotherDeviceScreen').callsFake(() => p());
sinon.spy(view, 'createExperiment');
sinon.spy(notifier, 'trigger');
});
describe('user is part of treatment', () => {
it('sets up experiment, delegates to `navigateToConnectAnotherDeviceScreen`', () => {
sinon.stub(view, 'getExperimentGroup').callsFake(() => 'treatment');
return view.navigateToConnectAnotherDeviceOnSigninScreen(account)
.then(() => {
assert.isTrue(notifier.trigger.calledWith('flow.initialize'));
assert.isTrue(view.createExperiment.calledOnce);
assert.isTrue(view.createExperiment.calledWith('sendSms', 'cadOnSignin.treatment'));
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledOnce);
assert.isTrue(view.navigateToConnectAnotherDeviceScreen.calledWith(account));
});
});
});
describe('user is part of control', () => {
it('sets up experiment, does nothing more', () => {
sinon.stub(view, 'getExperimentGroup').callsFake(() => 'control');
return view.navigateToConnectAnotherDeviceOnSigninScreen(account)
.then(() => {
assert.isTrue(notifier.trigger.calledWith('flow.initialize'));
assert.isTrue(view.createExperiment.calledOnce);
assert.isTrue(view.createExperiment.calledWith('sendSms', 'cadOnSignin.control'));
assert.isFalse(view.navigateToConnectAnotherDeviceScreen.called);
});
});
});
});
describe('isEligibleForConnectAnotherDevice', () => {
describe('user is completing sign-in', () => {
beforeEach(() => {
sinon.stub(user, 'getSignedInAccount').callsFake(() => {
return {
isDefault: () => true
};
});
sinon.stub(view, 'isSignUp').callsFake(() => false);
});
it('returns `false`', () => {
assert.isFalse(view.isEligibleForConnectAnotherDevice(account));
});
it('returns true for signup if a different user is not signed in', () => {
sinon.stub(user, 'isAnotherAccountSignedIn').callsFake(() => false);
assert.isTrue(view.isEligibleForConnectAnotherDevice(account));
});
describe('no user signed in', () => {
beforeEach(() => {
sinon.stub(user, 'getSignedInAccount').callsFake(() => {
return {
isDefault: () => true
};
});
});
it('returns `true`', () => {
assert.isTrue(view.isEligibleForConnectAnotherDevice(account));
});
});
describe('different user signed in', () => {
beforeEach(() => {
sinon.stub(user, 'getSignedInAccount').callsFake(() => {
return {
isDefault: () => false
};
});
sinon.stub(user, 'isSignedInAccount').callsFake(() => false);
});
it('returns `false`', () => {
assert.isFalse(view.isEligibleForConnectAnotherDevice(account));
});
});
describe('same user signed in', () => {
beforeEach(() => {
sinon.stub(user, 'getSignedInAccount').callsFake(() => {
return {
isDefault: () => false
};
});
sinon.stub(user, 'isSignedInAccount').callsFake(() => true);
});
it('returns `true`', () => {
assert.isTrue(view.isEligibleForConnectAnotherDevice(account));
});
it('returns false for signup if different user signed in', () => {
sinon.stub(user, 'isAnotherAccountSignedIn').callsFake(() => true);
assert.isFalse(view.isEligibleForConnectAnotherDevice(account));
});
});
@ -242,26 +273,6 @@ define(function (require, exports, module) {
});
describe('_areSmsRequirementsMet', () => {
describe('user is signing in', () => {
beforeEach(() => {
sinon.stub(view, 'isSignUp').callsFake(() => false);
sinon.stub(view, 'isInExperiment').callsFake(() => true);
sinon.stub(view, 'getUserAgent').callsFake(() => {
return {
isAndroid: () => false,
isIos: () => false
};
});
sinon.stub(user, 'isAnotherAccountSignedIn').callsFake(() => false);
});
it('returns `false', () => {
assert.isFalse(view._areSmsRequirementsMet(account));
assert.isTrue(view.logFlowEvent.calledOnce);
assert.isTrue(view.logFlowEvent.calledWith('sms.ineligible.signin'));
});
});
describe('user is on Android', () => {
beforeEach(() => {
sinon.stub(view, 'isSignUp').callsFake(() => true);
@ -476,7 +487,7 @@ define(function (require, exports, module) {
assert.isTrue(notifier.trigger.calledWith('flow.initialize'));
assert.isTrue(view.navigate.calledOnce);
assert.isTrue(view.navigate.calledWith('connect_another_device', { account }));
assert.isTrue(view.navigate.calledWith('connect_another_device', { account, type: 'signin' }));
});
});
});
@ -495,7 +506,7 @@ define(function (require, exports, module) {
assert.isTrue(view.createExperiment.calledWith('sendSms', 'treatment'));
assert.isTrue(view.navigate.calledOnce);
assert.isTrue(view.navigate.calledWith('sms', { account, country: 'GB' }));
assert.isTrue(view.navigate.calledWith('sms', { account, country: 'GB', type: 'signin' }));
});
});
});
@ -509,7 +520,7 @@ define(function (require, exports, module) {
assert.isTrue(view.createExperiment.calledWith('sendSms', 'control'));
assert.isTrue(view.navigate.calledOnce);
assert.isTrue(view.navigate.calledWith('connect_another_device', { account }));
assert.isTrue(view.navigate.calledWith('connect_another_device', { account, type: 'signin' }));
assert.isTrue(view.logFlowEvent.calledOnce);
assert.isTrue(view.logFlowEvent.calledWith('sms.ineligible.control_group'));

Просмотреть файл

@ -133,6 +133,14 @@
assert.lengthOf(view.$('.marketing-link'), 2);
});
});
it('for signin, renders extra text', () => {
sinon.stub(view, 'isSignIn').callsFake(() => true);
return view.render()
.then(() => {
assert.include(view.$('.instructions').text().toLowerCase(), 'still adding devices');
});
});
});
describe('submit', () => {

Просмотреть файл

@ -38,6 +38,7 @@ function (Translator, Session) {
'../tests/spec/lib/experiments/base',
'../tests/spec/lib/experiments/grouping-rules/base',
'../tests/spec/lib/experiments/grouping-rules/communication-prefs',
'../tests/spec/lib/experiments/grouping-rules/connect-another-device-on-signin',
'../tests/spec/lib/experiments/grouping-rules/disabled-button-state',
'../tests/spec/lib/experiments/grouping-rules/email-first',
'../tests/spec/lib/experiments/grouping-rules/index',
@ -126,6 +127,7 @@ function (Translator, Session) {
'../tests/spec/views/app',
'../tests/spec/views/base',
'../tests/spec/views/behaviors/connect-another-device',
'../tests/spec/views/behaviors/connect-another-device-on-signin',
'../tests/spec/views/behaviors/halt',
'../tests/spec/views/behaviors/halt-if-browser-transitions',
'../tests/spec/views/behaviors/navigate',

Просмотреть файл

@ -140,9 +140,9 @@ define([
.then(testUrlInclude(CONNECT_ANOTHER_DEVICE_ENTRYPOINT));
},
'signin Fx Desktop, verify same browser': function () {
'signin Fx Desktop, verify same browser - control': function () {
const forceUA = UA_STRINGS['desktop_firefox'];
const query = { forceUA };
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control', forceUA };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(SIGNIN_DESKTOP_URL, selectors.SIGNIN.HEADER, { query }))
@ -157,6 +157,23 @@ define([
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'signin Fx Desktop, verify same browser - treatment': function () {
const forceUA = UA_STRINGS['desktop_firefox'];
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment', forceUA };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(SIGNIN_DESKTOP_URL, selectors.SIGNIN.HEADER, { query }))
.then(respondToWebChannelMessage(CHANNEL_COMMAND_CAN_LINK_ACCOUNT, { ok: true } ))
.then(fillOutSignIn(email, PASSWORD))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER));
},
'signup Fx Desktop, verify different Fx Desktop with another user already signed in': function () {
const signInEmail = TestHelpers.createEmail('sync{id}');
const signUpEmail = email;

Просмотреть файл

@ -28,22 +28,21 @@ define([
const PASSWORD = 'password';
let email;
const setupTest = thenify(function (options) {
options = options || {};
const setupTest = thenify(function (options = {}) {
const successSelector = options.blocked ? selectors.SIGNIN_UNBLOCK.HEADER :
options.preVerified ? selectors.CONFIRM_SIGNIN.HEADER :
selectors.CONFIRM_SIGNUP.HEADER;
const query = Object.assign({
context: 'fx_fennec_v1',
email: email,
service: 'sync'
}, options.query || {});
return this.parent
.then(clearBrowserState())
.then(createUser(email, PASSWORD, { preVerified: options.preVerified }))
.then(openForceAuth({ query: {
context: 'fx_fennec_v1',
email: email,
service: 'sync'
}}))
.then(openForceAuth({ query }))
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true } ))
.then(fillOutForceAuth(PASSWORD))
@ -64,11 +63,13 @@ define([
email = TestHelpers.createEmail('sync{id}');
},
'verified, verify same browser': function () {
return this.remote
.then(setupTest({ preVerified: true }))
'verified, verify same browser - control': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
.then(openVerificationLinkInNewTab(email, 0))
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -76,6 +77,20 @@ define([
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify same browser - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify different browser - from original tab\'s P.O.V.': function () {
return this.remote
.then(setupTest({ preVerified: true }))

Просмотреть файл

@ -46,7 +46,7 @@ define([
return this.parent
.then(clearBrowserState())
.then(createUser(email, PASSWORD, { preVerified: options.preVerified }))
.then(openPage(SIGNIN_PAGE_URL, selectors.SIGNIN.HEADER))
.then(openPage(SIGNIN_PAGE_URL, selectors.SIGNIN.HEADER, { query: options.query }))
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true } ))
.then(fillOutSignIn(email, PASSWORD))
.then(testElementExists(successSelector))
@ -60,17 +60,18 @@ define([
});
registerSuite({
name: 'Fx Fennec Sync v1 sign_in',
name: 'Fx Fennec Sync v1 signin',
beforeEach: function () {
email = TestHelpers.createEmail('sync{id}');
},
'verified, verify same browser': function () {
'verified, verify same browser - control': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
return this.remote
.then(setupTest(selectors.CONFIRM_SIGNIN.HEADER, { preVerified: true }))
.then(setupTest(selectors.CONFIRM_SIGNIN.HEADER, { preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -78,6 +79,19 @@ define([
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify same browser - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' };
return this.remote
.then(setupTest(selectors.CONFIRM_SIGNIN.HEADER, { preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify different browser - from original tab\'s P.O.V.': function () {
return this.remote
.then(setupTest(selectors.CONFIRM_SIGNIN.HEADER, { preVerified: true }))

Просмотреть файл

@ -62,16 +62,18 @@ define([
}));
},
'verified, verify same browser': function () {
'verified, verify same browser - control': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
return this.remote
.then(setupTest({ preVerified: true }))
.then(setupTest({ preVerified: true, query }))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(clearBrowserNotifications())
.then(openVerificationLinkInNewTab(email, 0))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -80,6 +82,26 @@ define([
.then(noSuchBrowserNotification('fxaccounts:login'));
},
'verified, verify same browser - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(clearBrowserNotifications())
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(noSuchBrowserNotification('fxaccounts:login'));
},
'verified, verify different browser - from original tab\'s P.O.V.': function () {
return this.remote
.then(setupTest({ preVerified: true }))

Просмотреть файл

@ -38,6 +38,7 @@ define([
return this.remote
.then(clearBrowserState({ force: true }));
},
'signup': function () {
return this.remote
.then(openPage(PAGE_URL, selectors.ENTER_EMAIL.HEADER, {
@ -82,10 +83,13 @@ define([
.then(testIsBrowserNotified('fxaccounts:can_link_account'));
},
'signin verified': function () {
'signin verified - control': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(PAGE_URL, selectors.ENTER_EMAIL.HEADER, {
// Note, query not passed here or else email-first is not used.
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
@ -100,7 +104,7 @@ define([
.then(click(selectors.SIGNIN_PASSWORD.SUBMIT, selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -108,6 +112,35 @@ define([
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'signin verified - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(PAGE_URL, selectors.ENTER_EMAIL.HEADER, {
// Note, query not passed here or else email-first is not used.
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
}))
.then(visibleByQSA(selectors.ENTER_EMAIL.SUB_HEADER))
.then(type(selectors.ENTER_EMAIL.EMAIL, email))
.then(click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(testElementValueEquals(selectors.SIGNIN_PASSWORD.EMAIL, email))
.then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD))
.then(click(selectors.SIGNIN_PASSWORD.SUBMIT, selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'signin unverified': function () {
return this.remote
.then(createUser(email, PASSWORD, { preVerified: false }))
@ -141,15 +174,15 @@ define([
'email specified by relier, not registered': function () {
return this.remote
.then(openPage(PAGE_URL, selectors.SIGNUP_PASSWORD.HEADER, {
query: {
email
},
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
}))
.then(testElementValueEquals(selectors.SIGNUP_PASSWORD.EMAIL, email));
.then(openPage(PAGE_URL, selectors.SIGNUP_PASSWORD.HEADER, {
query: {
email
},
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
}))
.then(testElementValueEquals(selectors.SIGNUP_PASSWORD.EMAIL, email));
},
'email specified by relier, registered': function () {

Просмотреть файл

@ -79,9 +79,9 @@ define([
.then(clearBrowserState({ force: true }));
},
'verified, verify same browser': function () {
'verified, verify same browser - control': function () {
const forceUA = UA_STRINGS['ios_firefox_6_1'];
const query = { forceUA };
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control', forceUA };
return this.remote
.then(setupTest({ preVerified: true, query }))
@ -94,6 +94,21 @@ define([
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify same browser - treatment': function () {
const forceUA = UA_STRINGS['ios_firefox_6_1'];
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment', forceUA };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, verify different browser - from original tab\'s P.O.V.': function () {
const forceUA = UA_STRINGS['ios_firefox_6_1'];
return this.remote

Просмотреть файл

@ -128,10 +128,13 @@ define([
.then(testIsBrowserNotified('fxaccounts:can_link_account'));
},
'signin verified': function () {
'signin verified - control ': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(INDEX_PAGE_URL, selectors.ENTER_EMAIL.HEADER, {
// Note, query not passed here or else email-first is not used.
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
@ -147,7 +150,7 @@ define([
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -158,6 +161,39 @@ define([
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'signin verified - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' };
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(INDEX_PAGE_URL, selectors.ENTER_EMAIL.HEADER, {
// Note, query not passed here or else email-first is not used.
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true }
}
}))
.then(visibleByQSA(selectors.ENTER_EMAIL.SUB_HEADER))
.then(type(selectors.ENTER_EMAIL.EMAIL, email))
.then(click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(testElementValueEquals(selectors.SIGNIN_PASSWORD.EMAIL, email))
.then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD))
.then(click(selectors.SIGNIN_PASSWORD.SUBMIT, selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
// We do not expect the verification poll to occur. The poll
// will take a few seconds to complete if it erroneously occurs.
// Add an affordance just in case the poll happens unexpectedly.
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'signin unverified': function () {
return this.remote
.then(createUser(email, PASSWORD, { preVerified: false }))

Просмотреть файл

@ -42,7 +42,7 @@ define([
email = TestHelpers.createEmail('sync{id}');
},
'with a registered email, no uid, verify same browser': function () {
'with a registered email, no uid, verify same browser - control': function () {
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(function (accountInfo) {
@ -51,6 +51,8 @@ define([
context: 'fx_desktop_v3',
email: email,
forceAboutAccounts: 'true',
forceExperiment: 'cadOnSignin',
forceExperimentGroup: 'control',
service: 'sync'
}
}).call(this);
@ -62,7 +64,7 @@ define([
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0))
.then(openVerificationLinkInNewTab(email, 0, { query: { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' }}))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
@ -71,6 +73,37 @@ define([
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'with a registered email, no uid, verify same browser': function () {
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(function (accountInfo) {
return openForceAuth({
query: {
context: 'fx_desktop_v3',
email: email,
forceAboutAccounts: 'true',
forceExperiment: 'cadOnSignin',
forceExperimentGroup: 'treatment',
service: 'sync'
}
}).call(this);
})
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true } ))
.then(fillOutForceAuth(PASSWORD))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(testIsBrowserNotified('fxaccounts:login'))
.then(openVerificationLinkInNewTab(email, 0, { query: { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'treatment' }}))
.switchToWindow('newwindow')
.then(testElementExists(selectors.CONNECT_ANOTHER_DEVICE.HEADER))
.then(closeCurrentWindow())
// about:accounts will take over post-verification, no transition
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'with a registered email, registered uid, verify same browser': function () {
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))

Просмотреть файл

@ -74,11 +74,26 @@ define([
.then(clearBrowserState());
},
'verified, verify same browser, new tab\'s P.O.V.': function () {
return this.remote
.then(setupTest({ preVerified: true }))
'verified, verify same browser, new tab\'s P.O.V - control': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
.then(openVerificationLinkInNewTab(email, 0))
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow());
// tests for the original tab are below.
},
'verified, verify same browser, new tab\'s P.O.V - treatment': function () {
const query = { forceExperiment: 'cadOnSignin', forceExperimentGroup: 'control' };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInNewTab(email, 0, { query }))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow());