diff --git a/test/e2e/change-password.spec.ts b/test/e2e/change-password.spec.ts new file mode 100644 index 0000000000..8d7a634e91 --- /dev/null +++ b/test/e2e/change-password.spec.ts @@ -0,0 +1,64 @@ +import { expect } from '@playwright/test'; +import { test } from './utils/fixtures'; +import { ChangePasswordPage } from './pages/change-password.page'; +import { changePassword } from './utils/testControl'; +import { LoginPage } from './pages/login.page'; +import { PageHeader } from './pages/page-header.page'; + +test.describe('E2E Change Password app', () => { + const newPassword = '12345678'; + let changePasswordPage: ChangePasswordPage; + + test.beforeAll(async ({ memberTab }) => { + changePasswordPage = new ChangePasswordPage(memberTab); + await changePasswordPage.goto(); + }); + + test.afterAll(async ({ member, request }) => { + // reset password back to original + await changePassword(request, member.username, member.password); + }); + + test('Refuses to allow form submission if the confirm input does not match', async () => { + await changePasswordPage.passwordInput.fill(newPassword); + await changePasswordPage.confirmInput.fill('blah12345'); + await expect (changePasswordPage.submitButton).toBeDisabled(); + }); + + test('Allows form submission if the confirm input matches', async () => { + await changePasswordPage.passwordInput.fill(newPassword); + await changePasswordPage.confirmInput.fill(newPassword); + await expect(changePasswordPage.submitButton).toBeEnabled(); + }); + + test('Should not allow a password less than 7 characters', async () => { + let shortPassword = '12345'; + await changePasswordPage.passwordInput.fill(shortPassword); + await changePasswordPage.confirmInput.fill(shortPassword); + await expect (changePasswordPage.submitButton).toBeDisabled(); + }); + + test('Can successfully change user\'s password after form submission', async ({ page, member }) => { + await changePasswordPage.passwordInput.fill(newPassword); + await changePasswordPage.confirmInput.fill(newPassword); + await expect (changePasswordPage.passwordMatchImage).toBeVisible(); + await expect (changePasswordPage.submitButton).toBeEnabled(); + await changePasswordPage.submitButton.click(); + // when password is changed successfully, a notice appears on the page + const messageSuccessfulUpdate = '[data-ng-bind-html="notice.message"] >> text=Password updated successfully'; + await changePasswordPage.page.waitForSelector(messageSuccessfulUpdate, {strict: false, state: 'attached'}); + expect (await changePasswordPage.noticeList.locator(messageSuccessfulUpdate).count() + ).toBeGreaterThan(0); + + // test login with new password + + // await logout(memberTab); // CANNOT do this as it invalidates the session stored in storageState.json! - 2022-03 RM + // await login(memberTab, memberTab.username, newPassword); + + const loginPage = new LoginPage(page); + await loginPage.loginAs(member.username, newPassword); + const pageHeader = new PageHeader(page); + await expect (pageHeader.myProjects.button).toBeVisible(); + }); + +}); diff --git a/test/e2e/pages/page-header.page.ts b/test/e2e/pages/page-header.page.ts new file mode 100644 index 0000000000..5e8e9d4a64 --- /dev/null +++ b/test/e2e/pages/page-header.page.ts @@ -0,0 +1,21 @@ +import { Locator, Page } from "@playwright/test"; + +type MyProjects = { + button: Locator; + links: Locator; +}; + +export class PageHeader { + readonly page: Page; + readonly myProjects: MyProjects; + readonly loginButton: Locator; + + constructor(page: Page) { + this.page = page; + this.myProjects = { + button: page.locator('#myProjectDropdownButton'), + links: page.locator('#myProjectDropdownMenu >> .dropdown-item') + }; + this.loginButton = page.locator('text=Login').nth(0); + } +} diff --git a/test/e2e/pages/projects.page.ts b/test/e2e/pages/projects.page.ts index bb90dd3054..141d615cd2 100644 --- a/test/e2e/pages/projects.page.ts +++ b/test/e2e/pages/projects.page.ts @@ -20,6 +20,4 @@ export class ProjectsPage { await this.page.goto(ProjectsPage.url); await expect(this.pageName).toBeVisible(); } - - // TODO: write feature request: implement a waiting spinning somthing indicator - create github issue as feature request } diff --git a/test/e2e/utils/fixtures.ts b/test/e2e/utils/fixtures.ts index a52367f181..91bb4b4dd0 100644 --- a/test/e2e/utils/fixtures.ts +++ b/test/e2e/utils/fixtures.ts @@ -4,32 +4,78 @@ import type { Browser, Page } from '@playwright/test'; import type { usernamesForFixture } from './userFixtures'; import constants from '../testConstants.json'; -export type UserTab = Page & { +export type UserDetails = { username: string, password: string, name: string, email: string, } +export type UserTab = Page & UserDetails; + +function setupUserDetails(obj: any, username: usernamesForFixture) { + obj.username = constants[`${username}Username`] ?? username; + obj.name = constants[`${username}Name`] ?? username; + obj.password = constants[`${username}Password`] ?? 'x'; + obj.email = constants[`${username}Email`] ?? `${username}@example.com`; +} + const userTab = (username: usernamesForFixture) => async ({ browser, browserName }: { browser: Browser, browserName: string}, use: (r: UserTab) => Promise) => { const storageState = `${browserName}-${username}-storageState.json`; const context = await browser.newContext({ storageState }) const page = await context.newPage(); const tab = page as UserTab; - tab.username = constants[`${username}Username`] ?? username; - tab.name = constants[`${username}Name`] ?? username; - tab.password = constants[`${username}Password`] ?? 'x'; - tab.email = constants[`${username}Email`] ?? `${username}@example.com`; + setupUserDetails(tab, username); await use(tab); - await tab.close(); - await context.close(); } -// Extend basic test by providing a "todoPage" fixture. +// Add user fixtures to test function +// Two kinds of fixtures: userTab and user, where "user" is one of "admin", "manager", "member", "member2", or "observer" +// The userTab fixture represents a browser tab (a "page" in Playwright terms) that's already logged in as that user +// The user fixture just carries that user's details (username, password, name and email) +// Note: "Tab" was chosen instead of "Page" to avoid confusion with Page Object Model classes like SiteAdminPage export const test = (base - .extend<{ adminTab: UserTab }>({ adminTab: userTab('admin') }) - .extend<{ managerTab: UserTab }>({ managerTab: userTab('manager') }) - .extend<{ memberTab: UserTab }>({ memberTab: userTab('member') }) - .extend<{ member2Tab: UserTab }>({ member2Tab: userTab('member2') }) - .extend<{ observerTab: UserTab }>({ observerTab: userTab('observer') }) + .extend<{ + adminTab: UserTab, + managerTab: UserTab, + memberTab: UserTab, + member2Tab: UserTab, + observerTab: UserTab, + admin: UserDetails, + manager: UserDetails, + member: UserDetails, + member2: UserDetails, + observer: UserDetails, + }>({ + adminTab: userTab('admin'), + managerTab: userTab('manager'), + memberTab: userTab('member'), + member2Tab: userTab('member2'), + observerTab: userTab('observer'), + admin: async ({}, use) => { + let admin = {} as UserDetails; + setupUserDetails(admin, 'admin'); + await use(admin); + }, + manager: async ({}, use) => { + let manager = {} as UserDetails; + setupUserDetails(manager, 'manager'); + await use(manager); + }, + member: async ({}, use) => { + let member = {} as UserDetails; + setupUserDetails(member, 'member'); + await use(member); + }, + member2: async ({}, use) => { + let member2 = {} as UserDetails; + setupUserDetails(member2, 'member2'); + await use(member2); + }, + observer: async ({}, use) => { + let observer = {} as UserDetails; + setupUserDetails(observer, 'observer'); + await use(observer); + } + }) ); diff --git a/test/e2e/utils/login.ts b/test/e2e/utils/login.ts index 362dd2642a..36a64d3047 100644 --- a/test/e2e/utils/login.ts +++ b/test/e2e/utils/login.ts @@ -1,5 +1,6 @@ import { Browser, Page } from '@playwright/test'; import constants from '../testConstants.json'; +import type { usernamesForFixture } from './userFixtures'; export async function login(page: Page, username: string, password: string) { await page.goto('/auth/login'); @@ -15,7 +16,7 @@ export async function logout(page: Page) { return await page.goto('/auth/logout'); } -export function getLoginInfo(name: string) { +export function getLoginInfo(name: usernamesForFixture) { const usernameKey = `${name}Username`; const passwordKey = `${name}Password`; if (Object.hasOwnProperty.call(constants, usernameKey)) { @@ -28,7 +29,7 @@ export function getLoginInfo(name: string) { } } -export function loginAs(page: Page, name: string) { +export function loginAs(page: Page, name: usernamesForFixture) { const { username, password } = getLoginInfo(name); return login(page, username, password); }