Merge pull request #5472 from mozilla/issue-5197-transition-in-about-accounts r=@vbudhram

feat(sync): Transition screens after login message in Fx >= 57
This commit is contained in:
Shane Tomlinson 2017-09-14 14:10:45 +01:00 коммит произвёл GitHub
Родитель 16ff59e2a1 cc83734cfd
Коммит 65588f6f20
27 изменённых файлов: 520 добавлений и 197 удалений

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

@ -6,15 +6,19 @@
* Get the user agent string, or a user agent parser.
*
* Requires `this.window` to be set.
* Requires the `SearchParamMixin`.
*/
define((require, exports, module) => {
'use strict';
const SearchParamMixin = require('lib/search-param-mixin');
const UserAgent = require('lib/user-agent');
module.exports = {
dependsOn: [
SearchParamMixin
],
/**
* Get the user-agent string. For functional testing
* purposes, first attempts to fetch a UA string from the

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

@ -425,6 +425,11 @@ define(function (require, exports, module) {
* uid?
*/
allowUidChange: false,
/**
* Does the browser handle screen transitions after
* an email verification?
*/
browserTransitionsAfterEmailVerification: true,
/**
* Should the signup page show the `Choose what to sync` checkbox
*/

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

@ -15,30 +15,21 @@ define(function (require, exports, module) {
const _ = require('underscore');
const FxSyncWebChannelAuthenticationBroker = require('./fx-sync-web-channel');
const HaltBehavior = require('views/behaviors/halt');
const NullBehavior = require('views/behaviors/null');
const p = require('lib/promise');
const HaltIfBrowserTransitions = require('views/behaviors/halt-if-browser-transitions');
var proto = FxSyncWebChannelAuthenticationBroker.prototype;
const proto = FxSyncWebChannelAuthenticationBroker.prototype;
const defaultBehaviors = proto.defaultBehaviors;
var FxDesktopV2AuthenticationBroker = FxSyncWebChannelAuthenticationBroker.extend({
defaultBehaviors: _.extend({}, proto.defaultBehaviors, {
// about:accounts displays its own screen after sign in, no need
// to show anything.
afterForceAuth: new HaltBehavior(),
// about:accounts displays its own screen after password reset, no
// need to show anything.
afterResetPasswordConfirmationPoll: new HaltBehavior(),
// about:accounts displays its own screen after sign in, no need
// to show anything.
afterSignIn: new HaltBehavior(),
// the browser is already polling, no need for the content server
// code to poll as well, otherwise two sets of polls are going on
// for the same user.
beforeSignUpConfirmationPoll: new HaltBehavior()
const FxDesktopV2AuthenticationBroker = FxSyncWebChannelAuthenticationBroker.extend({
defaultBehaviors: _.extend({}, defaultBehaviors, {
afterForceAuth: new HaltIfBrowserTransitions(defaultBehaviors.afterForceAuth),
afterResetPasswordConfirmationPoll: new HaltIfBrowserTransitions(defaultBehaviors.afterResetPasswordConfirmationPoll),
afterSignIn: new HaltIfBrowserTransitions(defaultBehaviors.afterSignIn),
beforeSignUpConfirmationPoll: new HaltIfBrowserTransitions(defaultBehaviors.beforeSignUpConfirmationPoll),
}),
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
browserTransitionsAfterEmailVerification: true,
chooseWhatToSyncCheckbox: false,
chooseWhatToSyncWebV1: true,
openWebmailButtonVisible: true
@ -46,15 +37,6 @@ define(function (require, exports, module) {
type: 'fx-desktop-v2',
afterResetPasswordConfirmationPoll (/*account*/) {
// this is only called if the user verifies in the same browser.
// With Fx's E10s enabled, the account data only contains an
// unwrapBKey and keyFetchToken, not enough to sign in the user.
// Luckily, with WebChannels, the verification page can send
// the data to the browser and everybody is happy
return p(new HaltBehavior());
},
afterCompleteResetPassword (account) {
// See the note in afterResetPasswordConfirmationPoll
return this._notifyRelierOfLogin(account)
@ -64,15 +46,7 @@ define(function (require, exports, module) {
fetch () {
return proto.fetch.call(this).then(() => {
if (! this.environment.isAboutAccounts()) {
// The default behavior of FxDesktop brokers is to halt before
// the signup confirmation poll because about:accounts takes care
// of polling and updating the UI. However if we are not in about:accounts
// we do not want the halting behavior.
this._behaviors.keys().forEach((behaviorName) => {
if (this.getBehavior(behaviorName).type === 'halt') {
this.setBehavior(behaviorName, new NullBehavior());
}
});
this.setCapability('browserTransitionsAfterEmailVerification', false);
}
});
}

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

@ -13,19 +13,35 @@ define(function (require, exports, module) {
'use strict';
const _ = require('underscore');
const Cocktail = require('cocktail');
const FxDesktopV2AuthenticationBroker = require('./fx-desktop-v2');
const UserAgentMixin = require('lib/user-agent-mixin');
var proto = FxDesktopV2AuthenticationBroker.prototype;
const proto = FxDesktopV2AuthenticationBroker.prototype;
var FxDesktopV3AuthenticationBroker = FxDesktopV2AuthenticationBroker.extend({
const FxDesktopV3AuthenticationBroker = FxDesktopV2AuthenticationBroker.extend({
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
allowUidChange: true,
emailFirst: true
}),
type: 'fx-desktop-v3'
type: 'fx-desktop-v3',
fetch () {
return proto.fetch.call(this).then(() => {
const userAgent = this.getUserAgent();
if (userAgent.parseVersion().major >= 57) {
this.setCapability('browserTransitionsAfterEmailVerification', false);
}
});
}
});
Cocktail.mixin(
FxDesktopV3AuthenticationBroker,
UserAgentMixin
);
module.exports = FxDesktopV3AuthenticationBroker;
});

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

@ -268,14 +268,22 @@ define(function (require, exports, module) {
return ! _.difference(REQUIRED_LOGIN_FIELDS, loginFields).length;
},
/**
* Get login data from `account` to send to the browser.
* All returned keys have a defined value.
*
* @param {Object} account
* @returns {Object}
* @private
*/
_getLoginData (account) {
const loginData = account.pick(ALLOWED_LOGIN_FIELDS);
loginData.verified = !! loginData.verified;
loginData.verifiedCanLinkAccount = !! this._verifiedCanLinkEmail &&
!! this._verifiedCanLinkUid;
return loginData;
}
return _.omit(loginData, _.isUndefined);
}
}, {
REQUIRED_LOGIN_FIELDS
});

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

@ -0,0 +1,29 @@
/* 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 halts if the browser transitions after
* it detects an email verification. If browser does not
* transition, `defaultBehavior` is returned instead.
*/
define(function (require, exports, module) {
'use strict';
const HaltBehavior = require('views/behaviors/halt');
module.exports = function (defaultBehavior) {
const behavior = function (view) {
if (view.broker.getCapability('browserTransitionsAfterEmailVerification')) {
return new HaltBehavior();
}
return defaultBehavior;
};
behavior.type = 'halt-if-browser-transitions';
return behavior;
};
});

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

@ -40,6 +40,7 @@ define(function (require, exports, module) {
channel: channelMock,
window: windowMock
});
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
});
it('has the `signup` capability by default', function () {

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

@ -5,7 +5,7 @@
define(function (require, exports, module) {
'use strict';
const chai = require('chai');
const { assert } = require('chai');
const FxDesktopV2AuthenticationBroker = require('models/auth_brokers/fx-desktop-v2');
const NullChannel = require('lib/channels/null');
const p = require('lib/promise');
@ -13,16 +13,14 @@ define(function (require, exports, module) {
const User = require('models/user');
const WindowMock = require('../../../mocks/window');
var assert = chai.assert;
describe('models/auth_brokers/fx-desktop-v2', () => {
let account;
let broker;
let channelMock;
let user;
let windowMock;
describe('models/auth_brokers/fx-desktop-v2', function () {
var account;
var broker;
var channelMock;
var user;
var windowMock;
beforeEach(function () {
beforeEach(() => {
windowMock = new WindowMock();
channelMock = new NullChannel();
channelMock.send = () => {
@ -41,103 +39,112 @@ define(function (require, exports, module) {
channel: channelMock,
window: windowMock
});
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
});
it('has the `signup` capability', function () {
it('has the expected capabilities', () => {
assert.isTrue(broker.getCapability('browserTransitionsAfterEmailVerification'));
assert.isFalse(broker.getCapability('chooseWhatToSyncCheckbox'));
assert.isTrue(broker.getCapability('chooseWhatToSyncWebV1'));
assert.isTrue(broker.getCapability('openWebmailButtonVisible'));
assert.isTrue(broker.hasCapability('emailVerificationMarketingSnippet'));
assert.isTrue(broker.hasCapability('handleSignedInNotification'));
assert.isTrue(broker.hasCapability('signup'));
});
it('has the `handleSignedInNotification` capability', function () {
assert.isTrue(broker.hasCapability('handleSignedInNotification'));
});
it('has the `emailVerificationMarketingSnippet` capability', function () {
assert.isTrue(broker.hasCapability('emailVerificationMarketingSnippet'));
});
describe('createChannel', function () {
it('creates a channel', function () {
describe('createChannel', () => {
it('creates a channel', () => {
assert.ok(broker.createChannel());
});
});
describe('afterLoaded', function () {
it('sends a `fxaccounts:loaded` message', function () {
describe('afterLoaded', () => {
it('sends a `fxaccounts:loaded` message', () => {
return broker.afterLoaded()
.then(function () {
.then(() => {
assert.isTrue(channelMock.send.calledWith('fxaccounts:loaded'));
});
});
});
describe('afterForceAuth', function () {
it('notifies the channel with `fxaccounts:login`, halts', function () {
describe('afterForceAuth', () => {
it('notifies the channel with `fxaccounts:login`, halts if brower transitions', () => {
return broker.afterForceAuth(account)
.then(function (result) {
assert.isTrue(channelMock.send.calledWith('fxaccounts:login'));
assert.isTrue(result.halt);
assert.equal(result.type, 'halt-if-browser-transitions');
});
});
});
describe('afterSignIn', function () {
it('notifies the channel with `fxaccounts:login`, halts', function () {
describe('afterSignIn', () => {
it('notifies the channel with `fxaccounts:login`, halts if browser transitions', () => {
return broker.afterSignIn(account)
.then(function (result) {
assert.isTrue(channelMock.send.calledWith('fxaccounts:login'));
assert.isTrue(result.halt);
assert.equal(result.type, 'halt-if-browser-transitions');
});
});
});
describe('beforeSignUpConfirmationPoll', function () {
it('notifies the channel with `fxaccounts:login`, halts', function () {
describe('beforeSignUpConfirmationPoll', () => {
it('notifies the channel with `fxaccounts:login`, halts if browser transitions', () => {
return broker.beforeSignUpConfirmationPoll(account)
.then(function (result) {
assert.isTrue(channelMock.send.calledWith('fxaccounts:login'));
assert.isTrue(result.halt);
assert.equal(result.type, 'halt-if-browser-transitions');
});
});
});
describe('afterResetPasswordConfirmationPoll', function () {
var result;
beforeEach(function () {
describe('afterResetPasswordConfirmationPoll', () => {
let result;
beforeEach(() => {
// With Fx's E10s enabled, the account data only contains an
// unwrapBKey and keyFetchToken, not enough to sign in the user.
// Luckily, with WebChannels, the verification page can send
// the data to the browser and everybody is happy.
account = user.initAccount({
keyFetchToken: 'key-fetch-token',
unwrapBKey: 'unwrap-b-key'
});
broker._hasRequiredLoginFields.restore();
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => false);
return broker.afterResetPasswordConfirmationPoll(account)
.then(function (_result) {
result = _result;
});
});
it('does not notify the channel', function () {
it('does not notify the channel, halts if browser transitions', () => {
assert.isFalse(channelMock.send.called);
});
it('halts', function () {
assert.isTrue(result.halt);
assert.equal(result.type, 'halt-if-browser-transitions');
});
});
describe('afterCompleteResetPassword', function () {
var result;
beforeEach(function () {
describe('afterCompleteResetPassword', () => {
let result;
beforeEach(() => {
return broker.afterCompleteResetPassword(account)
.then(function (_result) {
result = _result;
});
});
it('notifies the channel with `fxaccounts:login`', function () {
it('notifies the channel with `fxaccounts:login`', () => {
assert.isTrue(channelMock.send.calledWith('fxaccounts:login'));
});
it('does not halt', function () {
it('does not halt', () => {
assert.isFalse(!! result.halt);
});
});
describe('afterChangePassword', function () {
it('does not notify channel with `fxaccounts:change_password`', function () {
describe('afterChangePassword', () => {
it('does not notify channel with `fxaccounts:change_password`', () => {
// The message is sent over the WebChannel by the global WebChannel, no
// need ot send it from within the auth broker too.
return broker.afterChangePassword(account)
@ -147,50 +154,31 @@ define(function (require, exports, module) {
});
});
describe('afterDeleteAccount', function () {
it('notifies the channel with `fxaccounts:delete_account`', function () {
describe('afterDeleteAccount', () => {
it('notifies the channel with `fxaccounts:delete_account`', () => {
account.set('uid', 'uid');
return broker.afterDeleteAccount(account)
.then(function () {
.then(() => {
assert.isTrue(channelMock.send.calledWith('fxaccounts:delete_account'));
});
});
});
it('disables the `chooseWhatToSyncCheckbox` capability', function () {
it('disables the `chooseWhatToSyncCheckbox` capability', () => {
return broker.fetch()
.then(function () {
.then(() => {
assert.isFalse(broker.hasCapability('chooseWhatToSyncCheckbox'));
});
});
describe('fetch', function () {
it('uses halt behavior with about:accounts', function () {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(function () {
return true;
});
describe('fetch', () => {
it('sets `browserTransitionsAfterEmailVerification` to false if not about:accounts', () => {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(() => false);
return broker.fetch()
.then(function () {
assert.equal(broker.getBehavior('afterForceAuth').type, 'halt');
assert.equal(broker.getBehavior('afterResetPasswordConfirmationPoll').type, 'halt');
assert.equal(broker.getBehavior('afterSignIn').type, 'halt');
assert.equal(broker.getBehavior('beforeSignUpConfirmationPoll').type, 'halt');
});
});
it('uses null behavior with web flow', function () {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(function () {
return false;
});
return broker.fetch()
.then(function () {
assert.equal(broker.getBehavior('afterForceAuth').type, 'null');
assert.equal(broker.getBehavior('afterResetPasswordConfirmationPoll').type, 'null');
assert.equal(broker.getBehavior('afterSignIn').type, 'null');
assert.equal(broker.getBehavior('beforeSignUpConfirmationPoll').type, 'null');
.then(() => {
assert.isFalse(broker.getCapability('browserTransitionsAfterEmailVerification'));
});
});
});

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

@ -5,18 +5,16 @@
define(function (require, exports, module) {
'use strict';
const chai = require('chai');
const { assert } = require('chai');
const FxDesktopV3AuthenticationBroker = require('models/auth_brokers/fx-desktop-v3');
const sinon = require('sinon');
const WindowMock = require('../../../mocks/window');
var assert = chai.assert;
describe('models/auth_brokers/fx-desktop-v3', function () {
describe('models/auth_brokers/fx-desktop-v3', () => {
var broker;
var windowMock;
beforeEach(function () {
beforeEach(() => {
windowMock = new WindowMock();
broker = new FxDesktopV3AuthenticationBroker({
@ -24,38 +22,28 @@ define(function (require, exports, module) {
});
});
describe('capabilities', function () {
it('has the `allowUidChange` capability', function () {
describe('capabilities', () => {
it('has the expected capabilities', () => {
assert.isTrue(broker.hasCapability('allowUidChange'));
assert.isTrue(broker.hasCapability('emailFirst'));
});
});
describe('fetch', function () {
it('uses halt behavior with about:accounts', function () {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(function () {
return true;
describe('fetch', () => {
it('sets `browserTransitionsAfterEmailVerification` to false if Fx >= 57', () => {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(() => true);
sinon.stub(broker, 'getUserAgent').callsFake(() => {
return {
parseVersion() {
return { major: 57 };
},
isFirefoxDesktop: () => true
};
});
return broker.fetch()
.then(function () {
assert.equal(broker.getBehavior('afterForceAuth').type, 'halt');
assert.equal(broker.getBehavior('afterResetPasswordConfirmationPoll').type, 'halt');
assert.equal(broker.getBehavior('afterSignIn').type, 'halt');
assert.equal(broker.getBehavior('beforeSignUpConfirmationPoll').type, 'halt');
});
});
it('uses null behavior with web flow', function () {
sinon.stub(broker.environment, 'isAboutAccounts').callsFake(function () {
return false;
});
return broker.fetch()
.then(function () {
assert.equal(broker.getBehavior('afterForceAuth').type, 'null');
assert.equal(broker.getBehavior('afterResetPasswordConfirmationPoll').type, 'null');
assert.equal(broker.getBehavior('afterSignIn').type, 'null');
assert.equal(broker.getBehavior('beforeSignUpConfirmationPoll').type, 'null');
.then(() => {
assert.isFalse(broker.getCapability('browserTransitionsAfterEmailVerification'));
});
});
});

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

@ -41,6 +41,7 @@ define(function (require, exports, module) {
relier: relier,
window: windowMock
});
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
sinon.spy(broker, 'send');
});

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

@ -46,6 +46,7 @@ define(function (require, exports, module) {
relier,
window: windowMock
});
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
});
afterEach(() => {

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

@ -33,6 +33,7 @@ define(function (require, exports, module) {
relier: relier,
window: windowMock
});
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
}
beforeEach(() => {

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

@ -22,9 +22,7 @@ define(function (require, exports, module) {
let user;
let windowMock;
function createAuthBroker (options) {
options = options || {};
function createAuthBroker (options = {}) {
broker = new FxSyncChannelAuthenticationBroker(_.extend({
channel: channelMock,
commands: {
@ -156,13 +154,21 @@ define(function (require, exports, module) {
const requiredAccountFields = _.without(FxSyncChannelAuthenticationBroker.REQUIRED_LOGIN_FIELDS, 'verified');
requiredAccountFields.forEach(function (fieldName) {
it('does not send a `login` message to the channel if the account does not have `' + fieldName + '`', () => {
it(`does not send a \`login\` message if the account does not have \`${fieldName}\``, () => {
account.unset(fieldName);
return broker._notifyRelierOfLogin(account)
.then(() => {
assert.isFalse(channelMock.send.called);
});
});
it(`does not send a \`login\` message if \`${fieldName}\` is undefined`, () => {
account.set(fieldName, undefined);
return broker._notifyRelierOfLogin(account)
.then(() => {
assert.isFalse(channelMock.send.called);
});
});
});
it('sends a `login` message to the channel using current account data', () => {

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

@ -0,0 +1,41 @@
/* 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 HaltIfBrowserTransitions = require('views/behaviors/halt-if-browser-transitions');
describe('views/behaviors/halt-if-browser-transitions', function () {
let defaultBehavior;
let behavior;
before(() => {
defaultBehavior = () => {};
behavior = new HaltIfBrowserTransitions(defaultBehavior);
});
it('returns a HaltBehavior if browser transitions after email verification', () => {
const returnedBehavior = behavior({
broker: {
getCapability: () => true
}
});
assert.equal(returnedBehavior.type, 'halt');
});
it('returns `defaultBehavior` if browser does not transition after email verification', () => {
const returnedBehavior = behavior({
broker: {
getCapability: () => false
}
});
assert.strictEqual(returnedBehavior, defaultBehavior);
});
});
});

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

@ -127,6 +127,7 @@ function (Translator, Session) {
'../tests/spec/views/base',
'../tests/spec/views/behaviors/connect-another-device',
'../tests/spec/views/behaviors/halt',
'../tests/spec/views/behaviors/halt-if-browser-transitions',
'../tests/spec/views/behaviors/navigate',
'../tests/spec/views/behaviors/null',
'../tests/spec/views/behaviors/settings',

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

@ -324,6 +324,15 @@ Used by functional tests to synthesize localStorage being disabled.
#### When to use
Should not be used by reliers. Should only be used by functional tests.
### `forceAboutAccounts`
Force Sync brokers to act as if the user opened FxA from within about:accounts.
#### Options
* `true`
#### When to use
Should not be used by reliers. Should only be used for Sync based functional tests.
### `forceExperiment`
Force a particular AB test.

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

@ -25,6 +25,7 @@ define([
'./functional/sync_v2_force_auth',
'./functional/sync_v3_email_first',
'./functional/sync_v3_force_auth',
'./functional/sync_v3_reset_password',
'./functional/sync_v3_settings',
'./functional/sync_v3_sign_in',
'./functional/sync_v3_sign_up',

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

@ -7,13 +7,14 @@ define([
'intern!object',
'tests/lib/helpers',
'tests/functional/lib/helpers',
'tests/functional/lib/selectors'
], function (intern, registerSuite, TestHelpers, FunctionalHelpers, selectors) {
'tests/functional/lib/selectors',
'tests/functional/lib/ua-strings'
], function (intern, registerSuite, TestHelpers, FunctionalHelpers, selectors, uaStrings) {
var bouncedEmail;
var deliveredEmail;
const PASSWORD = '12345678';
const SIGNIN_URL = `${intern.config.fxaContentRoot}signin?context=fx_desktop_v3&service=sync`;
const SIGNUP_URL = `${intern.config.fxaContentRoot}signup?context=fx_desktop_v3&service=sync`;
const SIGNIN_URL = `${intern.config.fxaContentRoot}signin?context=fx_desktop_v3&service=sync&automatedBrowser=true&forceAboutAccounts=true&forceUA=${encodeURIComponent(uaStrings.desktop_firefox_57)}`; //eslint-disable-line max-len
const SIGNUP_URL = `${intern.config.fxaContentRoot}signup?context=fx_desktop_v3&service=sync&automatedBrowser=true&forceAboutAccounts=true&forceUA=${encodeURIComponent(uaStrings.desktop_firefox_57)}`; //eslint-disable-line max-len
const clearBrowserState = FunctionalHelpers.clearBrowserState;
const click = FunctionalHelpers.click;
@ -34,7 +35,7 @@ define([
const visibleByQSA = FunctionalHelpers.visibleByQSA;
registerSuite({
name: 'sign_up with an email that bounces',
name: 'signup with an email that bounces',
beforeEach () {
bouncedEmail = TestHelpers.createEmail();
@ -44,8 +45,12 @@ define([
// ensure a fresh signup page is loaded. If this suite is
// run after a Sync suite, these tests try to use a Sync broker
// which results in a channel timeout.
.then(openPage(SIGNUP_URL, selectors.SIGNUP.HEADER))
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true }));
.then(openPage(SIGNUP_URL, selectors.SIGNUP.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null },
}
}));
},
afterEach () {
@ -116,8 +121,12 @@ define([
return this.parent
.then(clearBrowserState({ force: true }))
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(SIGNIN_URL, selectors.SIGNIN.HEADER))
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true }))
.then(openPage(SIGNIN_URL, selectors.SIGNIN.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null },
}
}))
.then(fillOutSignIn(email, PASSWORD))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
@ -130,7 +139,7 @@ define([
});
registerSuite({
name: 'sign_in with an email that bounces',
name: 'signin with an email that bounces',
afterEach () {
return this.remote.then(clearBrowserState());
@ -168,6 +177,7 @@ define([
return this.remote
.then(setUpBouncedSignIn())
.refresh()
.then(respondToWebChannelMessage('fxaccounts:fxa_status', { capabilities: null, signedInUser: null }))
.then(testElementExists(selectors.SIGNIN.HEADER));
}
});

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

@ -1116,7 +1116,7 @@ define([
var urlToOpen = FORCE_AUTH_URL + '?' + Querystring.stringify(options.query || {});
return this.parent
.then(openPage(urlToOpen, options.header || '#fxa-force-auth-header'));
.then(openPage(urlToOpen, options.header || '#fxa-force-auth-header', options));
});
/**

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

@ -121,6 +121,10 @@ define([], function () {
SUBMIT: 'button[type="submit"]',
SUCCESS: '.success'
},
RESET_PASSWORD_COMPLETE: {
HEADER: '#fxa-reset-password-complete-header',
SUB_HEADER: '.account-ready-service'
},
SETTINGS: {
CONTENT: '#fxa-settings-content',
HEADER: '#fxa-settings-header',

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

@ -11,6 +11,7 @@ define([], function () {
'desktop_firefox': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0',
'desktop_firefox_55': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0',
'desktop_firefox_56': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0',
'desktop_firefox_57': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:57.0) Gecko/20100101 Firefox/57.0',
'ios_firefox': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4',
'ios_firefox_6_0': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/6.0 Mobile/12F69 Safari/600.1.4',
'ios_firefox_6_1': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/6.1 Mobile/12F69 Safari/600.1.4',

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

@ -12,7 +12,7 @@ define([
var config = intern.config;
var PASSWORD = 'password';
var RESET_PASSWORD_URL = config.fxaContentRoot + 'reset_password?context=fx_desktop_v2&service=sync';
var RESET_PASSWORD_URL = config.fxaContentRoot + 'reset_password?context=fx_desktop_v2&service=sync&forceAboutAccounts=true&automatedBrowser=true';
var email;

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

@ -6,8 +6,9 @@ define([
'intern!object',
'tests/lib/helpers',
'tests/functional/lib/helpers',
'tests/functional/lib/selectors'
], function (registerSuite, TestHelpers, FunctionalHelpers, selectors) {
'tests/functional/lib/selectors',
'tests/functional/lib/ua-strings'
], function (registerSuite, TestHelpers, FunctionalHelpers, selectors, uaStrings) {
'use strict';
let email;
@ -23,6 +24,7 @@ define([
noPageTransition,
noSuchBrowserNotification,
openForceAuth,
openVerificationLinkInDifferentBrowser,
openVerificationLinkInNewTab,
respondToWebChannelMessage,
testElementDisabled,
@ -236,6 +238,35 @@ define([
// about:accounts will take over post-verification, no transition
.then(noPageTransition(selectors.SIGNIN_UNBLOCK.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'));
}
},
'verify from original tab\'s P.O.V., Fx >= 57': function () {
const forceUA = uaStrings['desktop_firefox_57'];
const query = {
automatedBrowser: true,
context: 'fx_desktop_v3',
email: email,
forceAboutAccounts: 'true',
forceUA,
service: 'sync',
uid: TestHelpers.createUID()
};
return this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openForceAuth({ query, webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null }
}}))
.then(noSuchBrowserNotification('fxaccounts:logout'))
.then(fillOutForceAuth(PASSWORD))
.then(testElementExists(selectors.CONFIRM_SIGNIN.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(openVerificationLinkInDifferentBrowser(email))
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'));
},
});
});

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

@ -0,0 +1,106 @@
/* 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([
'intern',
'intern!object',
'tests/lib/helpers',
'tests/functional/lib/helpers',
'tests/functional/lib/selectors',
'tests/functional/lib/ua-strings'
], function (intern, registerSuite, TestHelpers,
FunctionalHelpers, selectors, uaStrings) {
'use strict';
const config = intern.config;
const PASSWORD = 'password';
const RESET_PASSWORD_URL = `${config.fxaContentRoot}reset_password?context=fx_desktop_v3&service=sync&automatedBrowser=true&forceAboutAccounts=true`;
let email;
const {
clearBrowserState,
closeCurrentWindow,
createUser,
fillOutResetPassword,
fillOutCompleteResetPassword,
noPageTransition,
openPage,
openVerificationLinkInNewTab,
testElementExists,
testIsBrowserNotified,
testSuccessWasShown,
thenify,
} = FunctionalHelpers;
const setupTest = thenify(function (query) {
return this.parent
.then(createUser(email, PASSWORD, { preVerified: true }))
.then(openPage(RESET_PASSWORD_URL, selectors.RESET_PASSWORD.HEADER, {
query,
webChannelResponses: {
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null }
}
}))
.then(fillOutResetPassword(email))
.then(testElementExists(selectors.CONFIRM_RESET_PASSWORD.HEADER))
.then(openVerificationLinkInNewTab(email, 0))
.switchToWindow('newwindow')
.then(testElementExists(selectors.COMPLETE_RESET_PASSWORD.HEADER))
.then(fillOutCompleteResetPassword(PASSWORD, PASSWORD))
.then(testElementExists(selectors.RESET_PASSWORD_COMPLETE.HEADER))
.then(testElementExists(selectors.RESET_PASSWORD_COMPLETE.SUB_HEADER))
// the verification tab sends the WebChannel message. This fixes
// two problems: 1) initiating tab is closed, 2) The initiating
// tab when running in E10s does not have all the necessary data
// because localStorage is not shared.
.then(testIsBrowserNotified('fxaccounts:login'))
.then(closeCurrentWindow());
});
registerSuite({
name: 'Firefox Desktop Sync v3 reset password',
beforeEach: function () {
// timeout after 90 seconds
this.timeout = 90000;
email = TestHelpers.createEmail();
return this.remote.then(clearBrowserState());
},
teardown: function () {
// clear localStorage to avoid polluting other tests.
return this.remote.then(clearBrowserState());
},
'reset password, verify same browser, Fx <= 56': function () {
const query = { forceUA: uaStrings['desktop_firefox_56'], };
return this.remote
.then(setupTest(query))
// In fx <= 56, about:accounts takes over the screen, no need to transition
.then(noPageTransition(selectors.CONFIRM_RESET_PASSWORD.HEADER, 5000))
.then(testSuccessWasShown());
},
'reset password, verify same browser, Fx >= 57': function () {
const query = { forceUA: uaStrings['desktop_firefox_57'] };
return this.remote
.then(setupTest(query))
// In fx >= 57, about:accounts expects FxA to transition after email verification
.then(testElementExists(selectors.RESET_PASSWORD_COMPLETE.HEADER));
},
});
});

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

@ -7,12 +7,13 @@ define([
'intern!object',
'tests/lib/helpers',
'tests/functional/lib/helpers',
'tests/functional/lib/selectors'
], function (intern, registerSuite, TestHelpers, FunctionalHelpers, selectors) {
'tests/functional/lib/selectors',
'tests/functional/lib/ua-strings',
], function (intern, registerSuite, TestHelpers, FunctionalHelpers, selectors, uaStrings) {
'use strict';
const config = intern.config;
const PAGE_URL = `${config.fxaContentRoot}signin?context=fx_desktop_v3&service=sync&forceAboutAccounts=true`;
const PAGE_URL = `${config.fxaContentRoot}signin?context=fx_desktop_v3&service=sync&forceAboutAccounts=true&automatedBrowser=true`;
let email;
const PASSWORD = '12345678';
@ -48,8 +49,10 @@ define([
return this.parent
.then(clearBrowserState({ force: true }))
.then(createUser(signUpEmail, PASSWORD, { preVerified: options.preVerified }))
.then(openPage(PAGE_URL, selectors.SIGNIN.HEADER, { query: options.query }))
.then(respondToWebChannelMessage('fxaccounts:can_link_account', { ok: true } ))
.then(openPage(PAGE_URL, selectors.SIGNIN.HEADER, { query: options.query, webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null },
}}))
.then(fillOutSignIn(signInEmail, PASSWORD))
.then(testElementExists(successSelector))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
@ -71,19 +74,42 @@ define([
.then(clearBrowserState());
},
'verified, verify same browser': function () {
'verified, verify same browser, new tab\'s P.O.V.': function () {
return this.remote
.then(setupTest({ preVerified: true }))
.then(openVerificationLinkInNewTab(email, 0))
.switchToWindow('newwindow')
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER))
.then(closeCurrentWindow())
.then(closeCurrentWindow());
// tests for the original tab are below.
},
'Fx <= 56, verified, verify same browser, original tab\'s P.O.V.': function () {
const forceUA = uaStrings['desktop_firefox_56'];
const query = { forceUA };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInDifferentBrowser(email, 0))
// about:accounts will take over post-verification, no transition
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'Fx >= 57, verified, verify same browser, original tab\'s P.O.V.': function () {
const forceUA = uaStrings['desktop_firefox_57'];
const query = { forceUA };
return this.remote
.then(setupTest({ preVerified: true, query }))
.then(openVerificationLinkInDifferentBrowser(email, 0))
// about:accounts does not take over post-verification in Fx >= 57
.then(testElementExists(selectors.SIGNIN_COMPLETE.HEADER));
},
'verified, resend email, verify same browser': function () {
return this.remote
.then(setupTest({ preVerified: true }))
@ -101,16 +127,6 @@ define([
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'verified, verify different browser - from original tab\'s P.O.V.': function () {
return this.remote
.then(setupTest({ preVerified: true }))
.then(openVerificationLinkInDifferentBrowser(email))
// about:accounts will take over post-verification, no transition
.then(noPageTransition(selectors.CONFIRM_SIGNIN.HEADER));
},
'unverified': function () {
// this test does a lot of waiting around, give it a little extra time
this.timeout = 60 * 1000;
@ -142,11 +158,13 @@ define([
.then(noPageTransition(selectors.CONFIRM_SIGNUP.HEADER));
},
'verified, blocked': function () {
'Fx <= 56, verified, blocked': function () {
email = TestHelpers.createEmail('blocked{id}');
const forceUA = uaStrings['desktop_firefox_56'];
const query = { forceUA };
return this.remote
.then(setupTest({ blocked: true, preVerified: true }))
.then(setupTest({ blocked: true, preVerified: true, query }))
.then(fillOutSignInUnblock(email, 0))
@ -155,6 +173,23 @@ define([
.then(testIsBrowserNotified('fxaccounts:login'));
},
'Fx >= 57, verified, blocked': function () {
email = TestHelpers.createEmail('blocked{id}');
const forceUA = uaStrings['desktop_firefox_57'];
const query = { forceUA };
return this.remote
.then(setupTest({ blocked: true, preVerified: true, query }))
.then(fillOutSignInUnblock(email, 0))
// about:accounts does not take over post-verification in Fx >= 57
// NOTE: the user should actually be sent to the "signin complete" screen,
// but there is a bug that I haven't figured out yet.
.then(testElementExists(selectors.SETTINGS.HEADER))
.then(testIsBrowserNotified('fxaccounts:login'));
},
'verified, blocked, incorrect email case': function () {
const signUpEmail = TestHelpers.createEmail('blocked{id}');
const signInEmail = signUpEmail.toUpperCase();

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

@ -17,6 +17,8 @@ define([
`forceUA=${uaStrings.desktop_firefox_55}&automatedBrowser=true`;
const SIGNUP_FX_56_PAGE_URL = `${config.fxaContentRoot}signup?context=fx_desktop_v3&service=sync&forceAboutAccounts=true&` +
`forceUA=${uaStrings.desktop_firefox_56}&automatedBrowser=true`;
const SIGNUP_FX_57_PAGE_URL = `${config.fxaContentRoot}signup?context=fx_desktop_v3&service=sync&forceAboutAccounts=true&` +
`forceUA=${uaStrings.desktop_firefox_57}&automatedBrowser=true`;
let email;
const PASSWORD = '12345678';
@ -40,7 +42,7 @@ define([
const visibleByQSA = FunctionalHelpers.visibleByQSA;
registerSuite({
name: 'Firefox Desktop Sync v3 sign_up',
name: 'Firefox Desktop Sync v3 signup',
beforeEach: function () {
email = TestHelpers.createEmail();
@ -51,16 +53,12 @@ define([
return this.remote.then(clearBrowserState());
},
'sign up, user verifies at CWTS': function () {
'Fx <= 56, user verifies at CWTS': function () {
return this.remote
.then(openPage(SIGNUP_FX_55_PAGE_URL, selectors.SIGNUP.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': {
ok: true
},
'fxaccounts:fxa_status': {
signedInUser: null
}
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { signedInUser: null },
}
}))
.then(visibleByQSA(selectors.SIGNUP.SUB_HEADER))
@ -77,6 +75,29 @@ define([
.then(testIsBrowserNotified('fxaccounts:login'));
},
'Fx >= 57, user verifies at CWTS': function () {
return this.remote
.then(openPage(SIGNUP_FX_57_PAGE_URL, selectors.SIGNUP.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null },
}
}))
.then(visibleByQSA(selectors.SIGNUP.SUB_HEADER))
.then(fillOutSignUp(email, PASSWORD))
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.HEADER))
.then(testIsBrowserNotified('fxaccounts:can_link_account'))
.then(openVerificationLinkInDifferentBrowser(email, 0))
// In fx >= 57, about:accounts does not take over.
// Expect a screen transition.
.then(testElementExists(selectors.SIGNUP_COMPLETE.HEADER))
// but the login message is sent automatically.
.then(testIsBrowserNotified('fxaccounts:login'));
},
'Fx <= 55, verify same browser': function () {
return this.remote
.then(openPage(SIGNUP_FX_55_PAGE_URL, selectors.SIGNUP.HEADER, {
@ -253,6 +274,46 @@ define([
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.HEADER))
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.ENGINE_ADDRESSES))
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.ENGINE_CREDIT_CARDS));
}
},
'verify from original tab\'s P.O.V., Fx <= 56': function () {
return this.remote
.then(openPage(SIGNUP_FX_56_PAGE_URL, selectors.SIGNUP.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null }
}
}))
.then(fillOutSignUp(email, PASSWORD))
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.HEADER))
.then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT))
.then(testElementExists(selectors.CONFIRM_SIGNUP.HEADER))
.then(openVerificationLinkInDifferentBrowser(email))
// about:accounts takes over, no screen transition
.then(noPageTransition(selectors.CONFIRM_SIGNUP.HEADER, 5000));
},
'verify from original tab\'s P.O.V., Fx >= 57': function () {
return this.remote
.then(openPage(SIGNUP_FX_57_PAGE_URL, selectors.SIGNUP.HEADER, {
webChannelResponses: {
'fxaccounts:can_link_account': { ok: true },
'fxaccounts:fxa_status': { capabilities: null, signedInUser: null }
}
}))
.then(fillOutSignUp(email, PASSWORD))
.then(testElementExists(selectors.CHOOSE_WHAT_TO_SYNC.HEADER))
.then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT))
.then(testElementExists(selectors.CONFIRM_SIGNUP.HEADER))
.then(openVerificationLinkInDifferentBrowser(email))
// about:accounts does not take over, expect a screen transition.
.then(testElementExists(selectors.SIGNUP_COMPLETE.HEADER));
},
});
});

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

@ -42,6 +42,7 @@ define([
'tests/functional/sign_in_blocked',
'tests/functional/sign_in_cached',
'tests/functional/sync_v3_force_auth',
'tests/functional/sync_v3_reset_password',
'tests/functional/sync_v3_settings',
'tests/functional/sync_v3_sign_in',
]);