Skip to content

Commit

Permalink
Merge pull request #15478 from mozilla/FXA-7248-react-reset-pwd-funct…
Browse files Browse the repository at this point in the history
…-tests

task(functional-tests): Add missing reset password tests in Playwright
  • Loading branch information
vbudhram authored Jun 29, 2023
2 parents ea25b55 + 9d605e5 commit e73668c
Show file tree
Hide file tree
Showing 20 changed files with 567 additions and 146 deletions.
4 changes: 2 additions & 2 deletions packages/functional-tests/lib/ci-reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class CIReporter implements Reporter {
}`
);
if (test.outcome() === 'unexpected') {
console.log(result.error.stack);
console.log(result.error.message);
console.log(result.error?.stack);
console.log(result.error?.message);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/functional-tests/lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class EmailClient {
emailAddress: string,
type: EmailType,
header?: EmailHeader,
timeout: number = 15000
timeout = 15000
) {
const expires = Date.now() + timeout;
while (Date.now() < expires) {
Expand Down
17 changes: 17 additions & 0 deletions packages/functional-tests/lib/react-flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseTarget } from './targets/base';

export function getReactFeatureFlagUrl(
target: BaseTarget,
path: string,
params?: string
) {
if (params) {
return `${target.contentServerUrl}${path}?showReactApp=true&${params}`;
} else {
return `${target.contentServerUrl}${path}?showReactApp=true`;
}
}
2 changes: 2 additions & 0 deletions packages/functional-tests/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ResetPasswordPage } from './resetPassword';
import { LegalPage } from './legal';
import { CookiesDisabledPage } from './cookiesDisabled';
import { PostVerifyPage } from './postVerify';
import { ResetPasswordReactPage } from './resetPasswordReact';

export function create(page: Page, target: BaseTarget) {
return {
Expand All @@ -43,6 +44,7 @@ export function create(page: Page, target: BaseTarget) {
totp: new TotpPage(page, target),
subscriptionManagement: new SubscriptionManagementPage(page, target),
resetPassword: new ResetPasswordPage(page, target),
resetPasswordReact: new ResetPasswordReactPage(page, target),
legal: new LegalPage(page, target),
cookiesDisabled: new CookiesDisabledPage(page, target),
postVerify: new PostVerifyPage(page, target),
Expand Down
4 changes: 2 additions & 2 deletions packages/functional-tests/pages/relier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class RelierPage extends BaseLayout {

async isLoggedIn() {
const login = this.page.locator('#loggedin');
await login.waitFor({ state: 'visible' });
await login.waitFor();
return login.isVisible();
}

Expand Down Expand Up @@ -60,7 +60,7 @@ export class RelierPage extends BaseLayout {
}

async promptNoneError() {
const error = this.page.locator('.error');
const error = await this.page.locator('.error');
return error.innerText();
}

Expand Down
40 changes: 17 additions & 23 deletions packages/functional-tests/pages/resetPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const selectors = {
RESET_PASSWORD_EXPIRED_HEADER: '#fxa-reset-link-expired-header',
RESET_PASSWORD_HEADER: '#fxa-reset-password-header',
CONFIRM_RESET_PASSWORD_HEADER: '#fxa-confirm-reset-password-header',
COMPLETE_RESET_PASSWORD_HEADER: '#fxa-complete-reset-password-header',
EMAIL: 'input[type=email]',
SUBMIT: 'button[type=submit]',
VPASSWORD: '#vpassword',
Expand All @@ -22,39 +23,32 @@ export class ResetPasswordPage extends BaseLayout {
}

async resetPasswordHeader() {
const resetPass = this.page.locator(selectors.RESET_PASSWORD_HEADER);
await resetPass.waitFor();
return resetPass.isVisible();
const header = this.page.locator(selectors.RESET_PASSWORD_HEADER);
await header.waitFor();
}

async confirmResetPasswordHeader() {
const resetPass = this.page.locator(
selectors.CONFIRM_RESET_PASSWORD_HEADER
);
await resetPass.waitFor();
return resetPass.isVisible();
const header = this.page.locator(selectors.CONFIRM_RESET_PASSWORD_HEADER);
await header.waitFor();
}

async completeResetPasswordHeader() {
const resetPass = this.page.locator(
selectors.RESET_PASSWORD_COMPLETE_HEADER
);
await resetPass.waitFor();
return resetPass.isVisible();
async completeResetPasswordHeader(page: BaseLayout['page'] = this.page) {
const header = page.locator(selectors.COMPLETE_RESET_PASSWORD_HEADER);
await header.waitFor();
}

async resetPasswordLinkExpiredHeader() {
const resetPass = this.page.locator(
selectors.RESET_PASSWORD_EXPIRED_HEADER
);
await resetPass.waitFor();
return resetPass.isVisible();
const header = this.page.locator(selectors.RESET_PASSWORD_EXPIRED_HEADER);
await header.waitFor();
}

async resetNewPassword(password: string) {
await this.page.locator(selectors.PASSWORD).fill(password);
await this.page.locator(selectors.VPASSWORD).fill(password);
await this.page.locator(selectors.SUBMIT).click();
async resetNewPassword(
password: string,
page: BaseLayout['page'] = this.page
) {
await page.locator(selectors.PASSWORD).fill(password);
await page.locator(selectors.VPASSWORD).fill(password);
await page.locator(selectors.SUBMIT).click();
}

async fillOutResetPassword(email) {
Expand Down
110 changes: 110 additions & 0 deletions packages/functional-tests/pages/resetPasswordReact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { getReactFeatureFlagUrl } from '../lib/react-flag';
import { BaseLayout } from './layout';

export class ResetPasswordReactPage extends BaseLayout {
readonly path = '';

goto(route = '/reset_password', query?: string) {
return this.page.goto(getReactFeatureFlagUrl(this.target, route, query));
}

// page can be passed in as an optional prop - useful when a test uses more than one page
getEmailValue(page: BaseLayout['page'] = this.page) {
return page.getByRole('textbox', { name: 'Email' }).inputValue();
}

async resetPasswordHeadingVisible(page: BaseLayout['page'] = this.page) {
const heading = page.getByRole('heading', {
// Full heading might include "continue to [relying party]"
name: /Reset password/,
});
await heading.waitFor();
}

async confirmResetPasswordHeadingVisible(
page: BaseLayout['page'] = this.page
) {
const heading = page.getByRole('heading', {
name: 'Reset email sent',
});
await heading.waitFor();
}

async completeResetPwdHeadingVisible(page: BaseLayout['page'] = this.page) {
const heading = page.getByRole('heading', {
name: 'Create new password',
});
await heading.waitFor();
}

async resetPwdConfirmedHeadingVisible(page: BaseLayout['page'] = this.page) {
const heading = page.getByRole('heading', {
name: 'Your password has been reset',
});
await heading.waitFor();
}

async confirmRecoveryKeyHeadingVisible(page: BaseLayout['page'] = this.page) {
const heading = page.getByRole('heading', {
name: 'Reset password with account recovery key',
});
await heading.waitFor();
}

async resetPwdLinkExpiredHeadingVisible(
page: BaseLayout['page'] = this.page
) {
const heading = page.getByRole('heading', {
name: 'Reset password link expired',
});
await heading.waitFor();
}

async submitNewPassword(
password: string,
page: BaseLayout['page'] = this.page
) {
await page.getByRole('textbox', { name: 'New password' }).fill(password);
await page
.getByRole('textbox', { name: 'Re-enter password' })
.fill(password);
await page.getByRole('button', { name: 'Reset password' }).click();
}

async submitRecoveryKey(key: string, page: BaseLayout['page'] = this.page) {
const recoveryKeyInput = page.getByRole('textbox');
const submitButton = page.getByRole('button');
await recoveryKeyInput.fill(key);
await submitButton.click();
}

async fillEmailToResetPwd(email, page: BaseLayout['page'] = this.page) {
await page.getByRole('textbox', { name: 'Email' }).fill(email);
await page.getByRole('button', { name: 'Begin reset' }).click();
}

async clickResendLink(page: BaseLayout['page'] = this.page) {
const resendLink = await page.getByRole('link', {
name: 'Not in inbox or spam folder? Resend',
});
await resendLink.waitFor();
await resendLink.click();
}

async resendSuccessMessageVisible(page: BaseLayout['page'] = this.page) {
await page.getByText(/Email resent/).waitFor();
}

async unknownAccountError(page: BaseLayout['page'] = this.page) {
await page.getByText('Unknown account').waitFor();
}

async addQueryParamsToLink(link: string, query: object) {
query = query || {};
const parsedLink = new URL(link);
for (const paramName in query) {
parsedLink.searchParams.set(paramName, query[paramName]);
}
return parsedLink.toString();
}
}
4 changes: 2 additions & 2 deletions packages/functional-tests/tests/oauth/resetPassword.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { test, expect } from '../../lib/fixtures/standard';
import { EmailHeader, EmailType } from '../../lib/email';

test.describe('oauth reset password', () => {
test.beforeEach(async ({ target, pages: { login } }) => {
test.beforeEach(async () => {
test.slow();
});

Expand All @@ -23,7 +23,7 @@ test.describe('oauth reset password', () => {
await login.clickForgotPassword();

// Verify reset password header
expect(await resetPassword.resetPasswordHeader()).toBe(true);
await resetPassword.resetPasswordHeader();

await resetPassword.fillOutResetPassword(credentials.email);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { test, expect } from '../../lib/fixtures/standard';
import { BaseTarget } from '../../lib/targets/base';

function getReactFeatureFlagUrl(target: BaseTarget, path: string) {
return `${target.contentServerUrl}${path}?showReactApp=true`;
}
import { getReactFeatureFlagUrl } from '../../lib/react-flag';

test.beforeEach(async ({ pages: { login } }) => {
// This test requires simple react routes to be enabled
Expand Down
10 changes: 1 addition & 9 deletions packages/functional-tests/tests/react-conversion/legal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { test, expect } from '../../lib/fixtures/standard';
import { BaseTarget } from '../../lib/targets/base';

function getReactFeatureFlagUrl(
target: BaseTarget,
path: string,
showReactApp = true
) {
return `${target.contentServerUrl}${path}?showReactApp=${showReactApp}`;
}
import { getReactFeatureFlagUrl } from '../../lib/react-flag';

test.beforeEach(async ({ pages: { login } }) => {
// This test requires simple react routes to be enabled
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import { test, expect } from '../../lib/fixtures/standard';
import { EmailHeader, EmailType } from '../../lib/email';
import { BaseTarget } from '../../lib/targets/base';

let key;
let hint;
let originalEncryptionKeys;

const NEW_PASSWORD = 'notYourAveragePassW0Rd';

function getReactFeatureFlagUrl(
target: BaseTarget,
path: string,
showReactApp = true
) {
return `${target.contentServerUrl}${path}?showReactApp=${showReactApp}`;
}

test.describe('recovery key react', () => {
test.beforeEach(
async ({
Expand Down Expand Up @@ -90,15 +81,15 @@ test.describe('recovery key react', () => {
credentials,
target,
page,
pages: { resetPasswordReact },
}) => {
await page.goto(getReactFeatureFlagUrl(target, '/reset_password'));
await resetPasswordReact.goto();

// Verify react page has been loaded
expect(await page.locator('#root').isEnabled()).toBeTruthy();
// Verify react page is loaded
await page.waitForSelector('#root');

await page.locator('input').fill(credentials.email);
await page.locator('text="Begin reset"').click();
await page.waitForURL(/confirm_reset_password/);
await resetPasswordReact.fillEmailToResetPwd(credentials.email);
await resetPasswordReact.confirmResetPasswordHeadingVisible();

// We need to append `&showReactApp=true` to reset link inorder to enroll in reset password experiment
let link = await target.email.waitForEmail(
Expand All @@ -110,19 +101,15 @@ test.describe('recovery key react', () => {

// Loads the React version
await page.goto(link);
expect(await page.locator('#root').isEnabled()).toBeTruthy();
// Verify react page is loaded
await page.waitForSelector('#root');

expect(
await page.locator('text="Enter account recovery key"').isEnabled()
).toBeTruthy();
await page.locator('input').fill(key);
await page.locator('text="Confirm account recovery key"').click();
await page.waitForURL(/account_recovery_reset_password/);
await resetPasswordReact.confirmRecoveryKeyHeadingVisible();

await page.locator('input[name="newPassword"]').fill(NEW_PASSWORD);
await page.locator('input[name="confirmPassword"]').fill(NEW_PASSWORD);
await resetPasswordReact.submitRecoveryKey(key);
await page.waitForURL(/account_recovery_reset_password/);

await page.locator('text="Reset password"').click();
await resetPasswordReact.submitNewPassword(NEW_PASSWORD);
await page.waitForURL(/reset_password_with_recovery_key_verified/);

// Attempt to login with new password
Expand All @@ -149,7 +136,9 @@ test.describe('recovery key react', () => {
// Cleanup requires setting this value to correct password
credentials.password = NEW_PASSWORD;

await page.locator('text="Generate a new account recovery key"').click();
await page
.getByRole('button', { name: 'Generate a new account recovery key' })
.click();
await page.waitForURL(/settings\/account_recovery/);
});
});
Loading

0 comments on commit e73668c

Please sign in to comment.