mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-04-04 14:53:33 -04:00
test(e2e): add auth, settings, social and organization page coverage
* Added Database checks to the onboarding flow * Added compatibility page setup Added more compatibility questions * Finished up the onboarding flow suite Added compatibility question tests and verifications Updated tests to cover Keywords and Headline changes recently made Updated tests to cover all of the big5 personality traits * . * Fix: Merge conflict * . * Fix: Added fix for None discriptive error issue #36 Updated signUp.spec.ts to use new fixture Updated Account information variable names Deleted "deleteUserFixture.ts" as it was incorporated into the "base.ts" file * Linting and Prettier * Minor cleaning * Organizing helper func * Added Google account to the Onboarding flow * . * Added account cleanup for google accounts * Started work on Sign-in tests Updated seedDatabase.ts to throw an error if the user already exists, to also add display names and usernames so they seedUser func acts like a normal basic user Some organising of the google auth code * Linting and Prettier * Added checks to the deleteUser func to check if the accout exists Added account deletion checks * Linting and Prettier * Added POM's for social and organisation page Updated settings POM * Formatting update, fixed homePage locator for signin * . * . * . * Coderabbitai fix's * Fix * Improve test utilities and stabilize onboarding flow tests * Changes requested * Seperated deletion tests from onboarding * Update `.coderabbit.yaml` with improved internationalization guidance and formatting adjustments * Clean up `.vscode/settings.json` and add it to `.gitignore` * Add Playwright E2E test guidelines to `.coderabbit.yaml` * Standardize and improve formatting in `TESTING.md` for better readability and consistency. * Refactor onboarding flow tests and related utilities; improve formatting and remove redundant tests. --------- Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
This commit is contained in:
committed by
GitHub
parent
09736cd49b
commit
d2c9d12b39
@@ -78,7 +78,7 @@ reviews:
|
||||
- Flag any hardcoded strings; they should be in the constants file.
|
||||
- Check for edge cases like null values or empty arrays.
|
||||
- Suggest performance optimizations where appropriate.
|
||||
|
||||
|
||||
Mobile best practices:
|
||||
- Proper use of hooks (useRouter, useFonts, useAssets)
|
||||
- Accessibility: touch targets min 44x44, screen reader support
|
||||
@@ -99,4 +99,22 @@ reviews:
|
||||
- Validate deep linking configurations
|
||||
|
||||
Internationalization:
|
||||
- User-visible strings should be externalized to resource files (useT())
|
||||
- User-visible strings should be externalized to JSON resource files in common/messages via
|
||||
```
|
||||
const t = useT()
|
||||
const message = t('key', 'english string')
|
||||
```
|
||||
|
||||
- path: "tests/e2e/**/*.ts"
|
||||
instructions: |
|
||||
Playwright E2E test guidelines for this repo:
|
||||
- Page objects live in `tests/e2e/web/pages/`. Each class wraps one page/route, holds only `private readonly` Locators, and exposes action methods.
|
||||
- All tests must use the `app` fixture (type `App`) from `tests/e2e/web/fixtures/base.ts`. Never instantiate page objects directly in a test.
|
||||
- Cross-page flows (actions spanning multiple pages) belong as methods on the `App` class, not as standalone helper functions.
|
||||
- Action methods in page objects must assert `expect(locator).toBeVisible()` before interacting.
|
||||
- Never use `page.waitForTimeout()`. Use Playwright's built-in auto-waiting or `waitForURL` / `waitForSelector`.
|
||||
- No hardcoded credentials in spec files; use `SPEC_CONFIG.ts` or account fixtures.
|
||||
- Test account cleanup must be done in fixture teardown (after `await use(...)`), not in `afterEach` hooks.
|
||||
- File and class names must use PascalCase (e.g., `CompatibilityPage.ts` / `class CompatibilityPage`).
|
||||
- No DB or Firebase calls inside page object classes; those belong in `tests/e2e/utils/`.
|
||||
- Flag any new page object not yet registered in `App`.
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -102,3 +102,5 @@ test-results
|
||||
**/coverage
|
||||
|
||||
*my-release-key.keystore
|
||||
|
||||
.vscode/settings.json
|
||||
@@ -348,6 +348,7 @@ jest.mock('path/to/module')
|
||||
* This creates an object containing all named exports from ./path/to/module
|
||||
*/
|
||||
import * as mockModule from 'path/to/module'
|
||||
|
||||
;(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue)
|
||||
```
|
||||
|
||||
@@ -705,6 +706,66 @@ Use this priority order for selecting elements in Playwright tests:
|
||||
|
||||
This hierarchy mirrors how users actually interact with your application, making tests more reliable and meaningful.
|
||||
|
||||
### Page Object Model (POM)
|
||||
|
||||
Tests often receive multiple page objects as fixtures (e.g. `homePage`, `authPage`, `profilePage`). This is the **Page
|
||||
Object Model** pattern — a way to organize selectors and actions by the area of the app they belong to.
|
||||
|
||||
**Page objects are not separate browser tabs.** They are all wrappers around the same underlying `page` instance. Each
|
||||
class simply encapsulates the selectors and actions relevant to one part of the UI:
|
||||
|
||||
```typescript
|
||||
class ProfilePage {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
async verifyDisplayName(name: string) {
|
||||
await expect(this.page.getByTestId('display-name')).toHaveText(name)
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsPage {
|
||||
constructor(private page: Page) {} // same page instance
|
||||
|
||||
async deleteAccount() {
|
||||
await this.page.getByRole('button', {name: 'Delete account'}).click()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why use POM instead of raw `page`?**
|
||||
|
||||
Without it, tests are full of inline selectors that are brittle and hard to read. POM moves implementation details into
|
||||
dedicated classes so that the test itself reads like a plain-English description of user behavior. When a selector
|
||||
changes, you fix it in one place.
|
||||
|
||||
```typescript
|
||||
// ❌ Without POM — noisy and brittle
|
||||
await page.locator('#email-input').fill(account.email)
|
||||
await page.locator('#submit-btn').click()
|
||||
await page.locator('[data-testid="skip-onboarding"]').click()
|
||||
// ...50 more lines of noise
|
||||
|
||||
// ✅ With POM — readable and maintainable
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, fakerAccount)
|
||||
await profilePage.verifyDisplayName(fakerAccount.display_name)
|
||||
```
|
||||
|
||||
**What happens if you call a method on the "wrong" page object?**
|
||||
|
||||
Nothing special — it still runs. Since all page objects share the same `page`, the method simply acts on whatever is
|
||||
currently rendered in the browser. Page objects do not track which screen you're on; that's your responsibility as the
|
||||
test author. If you call `profilePage.verifyDisplayName()` while the browser is showing the settings screen, the locator
|
||||
won't find its element and the test will **time out**.
|
||||
|
||||
```typescript
|
||||
// ⚠️ This fails at runtime if navigation hasn't happened yet
|
||||
await settingsPage.deleteAccount() // navigates away from profile
|
||||
await profilePage.verifyDisplayName(name) // locator not found → timeout
|
||||
```
|
||||
|
||||
Always ensure navigation has completed before calling methods that depend on a specific screen being visible.
|
||||
|
||||
### Setting up test data
|
||||
|
||||
Since the tests run in parallel (i.e., at the same time) and share the same database and Firebase emulator, it can
|
||||
@@ -802,7 +863,9 @@ These are seeded automatically by `yarn test:db:seed`:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For comprehensive troubleshooting guidance beyond testing-specific issues, see the [Troubleshooting Guide](TROUBLESHOOTING.md) which covers development environment setup, database and emulator issues, API problems, and more.
|
||||
For comprehensive troubleshooting guidance beyond testing-specific issues, see
|
||||
the [Troubleshooting Guide](TROUBLESHOOTING.md) which covers development environment setup, database and emulator
|
||||
issues, API problems, and more.
|
||||
|
||||
### Port already in use
|
||||
|
||||
@@ -885,4 +948,5 @@ To download the Playwright report from a failed CI run:
|
||||
3. Download `playwright-report`
|
||||
4. Open `index.html` in your browser
|
||||
|
||||
For performance testing guidance and benchmarking strategies, see the [Performance Optimization Guide](PERFORMANCE_OPTIMIZATION.md).
|
||||
For performance testing guidance and benchmarking strategies, see
|
||||
the [Performance Optimization Guide](PERFORMANCE_OPTIMIZATION.md).
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
SUBSTANCE_PREFERENCE_CHOICES,
|
||||
} from 'common/choices'
|
||||
|
||||
class UserAccountInformation {
|
||||
class UserAccountInformationForSeeding {
|
||||
name = faker.person.fullName()
|
||||
userName = faker.internet.displayName()
|
||||
email = faker.internet.email()
|
||||
user_id = faker.string.alpha(28)
|
||||
password = faker.internet.password()
|
||||
@@ -54,4 +55,4 @@ class UserAccountInformation {
|
||||
}
|
||||
}
|
||||
|
||||
export default UserAccountInformation
|
||||
export default UserAccountInformationForSeeding
|
||||
|
||||
@@ -6,7 +6,8 @@ export async function deleteFromDb(user_id: string) {
|
||||
const result = await db.query(deleteEntryById, [user_id])
|
||||
|
||||
if (!result.length) {
|
||||
throw new Error(`No user found with id: ${user_id}`)
|
||||
console.debug(`No user found with id: ${user_id}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Deleted data: ', {
|
||||
@@ -19,8 +20,8 @@ export async function deleteFromDb(user_id: string) {
|
||||
export async function userInformationFromDb(account: any) {
|
||||
const db = createSupabaseDirectClient()
|
||||
const queryUserById = `
|
||||
SELECT p.*
|
||||
FROM users AS p
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE username = $1
|
||||
`
|
||||
const userResults = await db.query(queryUserById, [account.username])
|
||||
|
||||
@@ -2,7 +2,10 @@ import axios from 'axios'
|
||||
|
||||
import {config} from '../web/SPEC_CONFIG'
|
||||
|
||||
export async function firebaseLogin(email: string, password: string) {
|
||||
export async function firebaseLoginEmailPassword(
|
||||
email: string | undefined,
|
||||
password: string | undefined,
|
||||
) {
|
||||
const login = await axios.post(
|
||||
`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SIGN_IN_PASSWORD}`,
|
||||
{
|
||||
@@ -13,15 +16,28 @@ export async function firebaseLogin(email: string, password: string) {
|
||||
)
|
||||
return login
|
||||
}
|
||||
|
||||
export async function getUserId(email: string, password: string) {
|
||||
try {
|
||||
const loginInfo = await firebaseLogin(email, password)
|
||||
const loginInfo = await firebaseLoginEmailPassword(email, password)
|
||||
return loginInfo.data.localId
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export async function findUser(idToken: string) {
|
||||
const response = await axios.post(
|
||||
`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.ACCOUNT_LOOKUP}`,
|
||||
{
|
||||
idToken,
|
||||
},
|
||||
)
|
||||
if (response?.data?.users?.length > 0) {
|
||||
return response.data.users[0]
|
||||
}
|
||||
}
|
||||
|
||||
export async function firebaseSignUp(email: string, password: string) {
|
||||
try {
|
||||
const response = await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SIGNUP}`, {
|
||||
@@ -46,8 +62,15 @@ export async function firebaseSignUp(email: string, password: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAccount(login: any) {
|
||||
await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.DELETE}`, {
|
||||
idToken: login.data.idToken,
|
||||
})
|
||||
export async function deleteAccount(idToken: any) {
|
||||
try {
|
||||
await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.DELETE}`, {
|
||||
idToken: idToken,
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.response?.data?.error?.message?.includes('USER_NOT_FOUND')) {
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {insert} from 'shared/supabase/utils'
|
||||
import {getUser} from 'shared/utils'
|
||||
|
||||
import UserAccountInformation from '../backend/utils/userInformation'
|
||||
import UserAccountInformationForSeeding from '../backend/utils/userInformation'
|
||||
import {firebaseSignUp} from './firebaseUtils'
|
||||
|
||||
/**
|
||||
@@ -16,7 +16,10 @@ import {firebaseSignUp} from './firebaseUtils'
|
||||
* @param userInfo - Class object containing information to create a user account generated by `fakerjs`.
|
||||
* @param profileType - Optional param used to signify how much information is used in the account generation.
|
||||
*/
|
||||
export async function seedDbUser(userInfo: UserAccountInformation, profileType?: string) {
|
||||
export async function seedDbUser(
|
||||
userInfo: UserAccountInformationForSeeding,
|
||||
profileType?: string,
|
||||
): Promise<Boolean> {
|
||||
const pg = createSupabaseDirectClient()
|
||||
const userId = userInfo.user_id
|
||||
const deviceToken = randomString()
|
||||
@@ -83,14 +86,14 @@ export async function seedDbUser(userInfo: UserAccountInformation, profileType?:
|
||||
blockedByUserIds: [],
|
||||
}
|
||||
|
||||
await pg.tx(async (tx: any) => {
|
||||
return pg.tx(async (tx: any) => {
|
||||
const preexistingUser = await getUser(userId, tx)
|
||||
if (preexistingUser) return
|
||||
if (preexistingUser) return false
|
||||
|
||||
await insert(tx, 'users', {
|
||||
id: userId,
|
||||
name: userInfo.name,
|
||||
username: cleanUsername(userInfo.name),
|
||||
username: cleanUsername(userInfo.userName),
|
||||
data: {},
|
||||
})
|
||||
|
||||
@@ -100,6 +103,7 @@ export async function seedDbUser(userInfo: UserAccountInformation, profileType?:
|
||||
})
|
||||
|
||||
await insert(tx, 'profiles', profileData)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,13 +111,17 @@ export async function seedUser(
|
||||
email?: string | undefined,
|
||||
password?: string | undefined,
|
||||
profileType?: string | undefined,
|
||||
displayName?: string | undefined,
|
||||
userName?: string | undefined,
|
||||
) {
|
||||
const userInfo = new UserAccountInformation()
|
||||
const userInfo = new UserAccountInformationForSeeding()
|
||||
if (email) userInfo.email = email
|
||||
if (password) userInfo.password = password
|
||||
if (displayName) userInfo.name = displayName
|
||||
if (userName) userInfo.userName = userName
|
||||
userInfo.user_id = await firebaseSignUp(userInfo.email, userInfo.password)
|
||||
if (userInfo.user_id) {
|
||||
await seedDbUser(userInfo, profileType ?? 'full')
|
||||
const created = await seedDbUser(userInfo, profileType ?? 'full')
|
||||
if (created) debug('User created in Firebase and Supabase:', userInfo.email)
|
||||
}
|
||||
debug('User created in Firebase and Supabase:', userInfo.email)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export const config = {
|
||||
BASE: 'http://localhost:9099/identitytoolkit.googleapis.com/v1',
|
||||
SIGNUP: '/accounts:signUp?key=fake-api-key',
|
||||
SIGN_IN_PASSWORD: '/accounts:signInWithPassword?key=fake-api-key',
|
||||
ACCOUNT_LOOKUP: '/accounts:lookup?key=fake-api-key',
|
||||
DELETE: '/accounts:delete?key=fake-api-key',
|
||||
},
|
||||
USERS: {
|
||||
|
||||
@@ -4,10 +4,14 @@ import {AuthPage} from '../pages/AuthPage'
|
||||
import {ComatibilityPage} from '../pages/compatibilityPage'
|
||||
import {HomePage} from '../pages/homePage'
|
||||
import {OnboardingPage} from '../pages/onboardingPage'
|
||||
import {OrganizationPage} from '../pages/organizationPage'
|
||||
import {ProfilePage} from '../pages/profilePage'
|
||||
import {SettingsPage} from '../pages/settingsPage'
|
||||
import {SignUpPage} from '../pages/signUpPage'
|
||||
import {SocialPage} from '../pages/socialPage'
|
||||
import {testAccounts, UserAccountInformation} from '../utils/accountInformation'
|
||||
import {deleteUser} from '../utils/deleteUser'
|
||||
import {getAuthAccountInfo} from '../utils/networkUtils'
|
||||
|
||||
export const test = base.extend<{
|
||||
homePage: HomePage
|
||||
@@ -15,29 +19,48 @@ export const test = base.extend<{
|
||||
signUpPage: SignUpPage
|
||||
profilePage: ProfilePage
|
||||
authPage: AuthPage
|
||||
settingsPage: SettingsPage
|
||||
socialPage: SocialPage
|
||||
organizationPage: OrganizationPage
|
||||
compatabilityPage: ComatibilityPage
|
||||
cleanUpUsers: void
|
||||
onboardingAccount: UserAccountInformation
|
||||
fakerAccount: UserAccountInformation
|
||||
specAccount: UserAccountInformation
|
||||
googleAccountOne: UserAccountInformation
|
||||
googleAccountTwo: UserAccountInformation
|
||||
}>({
|
||||
onboardingAccount: async ({}, use) => {
|
||||
const account = testAccounts.account_all_info() // email captured here
|
||||
const account = testAccounts.email_account_all_info() // email captured here
|
||||
await use(account)
|
||||
console.log('Cleaning up onboarding 1 account...')
|
||||
await deleteUser(account.email, account.password) // same account, guaranteed
|
||||
await deleteUser('Email/Password', account) // same account, guaranteed
|
||||
},
|
||||
fakerAccount: async ({}, use) => {
|
||||
const account = testAccounts.faker_account() // email captured here
|
||||
const account = testAccounts.faker_account()
|
||||
await use(account)
|
||||
console.log('Cleaning up faker account...')
|
||||
await deleteUser(account.email, account.password) // same account, guaranteed
|
||||
await deleteUser('Email/Password', account)
|
||||
},
|
||||
googleAccountOne: async ({page}, use) => {
|
||||
const account = testAccounts.google_account_one()
|
||||
const getAuthObject = await getAuthAccountInfo(page)
|
||||
await use(account)
|
||||
console.log('Cleaning up google account...')
|
||||
await deleteUser('Google', undefined, getAuthObject())
|
||||
},
|
||||
googleAccountTwo: async ({page}, use) => {
|
||||
const account = testAccounts.google_account_two()
|
||||
const getAuthObject = await getAuthAccountInfo(page)
|
||||
await use(account)
|
||||
console.log('Cleaning up google account...')
|
||||
await deleteUser('Google', undefined, getAuthObject())
|
||||
},
|
||||
specAccount: async ({}, use) => {
|
||||
const account = testAccounts.spec_account()
|
||||
await use(account)
|
||||
console.log('Cleaning up spec account...')
|
||||
await deleteUser(account.email, account.password)
|
||||
await deleteUser('Email/Password', account)
|
||||
},
|
||||
onboardingPage: async ({page}, use) => {
|
||||
const onboardingPage = new OnboardingPage(page)
|
||||
@@ -63,6 +86,18 @@ export const test = base.extend<{
|
||||
const compatibilityPage = new ComatibilityPage(page)
|
||||
await use(compatibilityPage)
|
||||
},
|
||||
settingsPage: async ({page}, use) => {
|
||||
const settingsPage = new SettingsPage(page)
|
||||
await use(settingsPage)
|
||||
},
|
||||
socialPage: async ({page}, use) => {
|
||||
const socialPage = new SocialPage(page)
|
||||
await use(socialPage)
|
||||
},
|
||||
organizationPage: async ({page}, use) => {
|
||||
const organizationPage = new OrganizationPage(page)
|
||||
await use(organizationPage)
|
||||
},
|
||||
})
|
||||
|
||||
export {expect} from '@playwright/test'
|
||||
|
||||
@@ -1,33 +1,64 @@
|
||||
import {expect, Page, test as base} from '@playwright/test'
|
||||
|
||||
import {seedUser} from '../../utils/seedDatabase'
|
||||
import {test as base} from '@playwright/test'
|
||||
import {AuthPage} from '../pages/AuthPage'
|
||||
import {config} from '../SPEC_CONFIG'
|
||||
import {HomePage} from '../pages/homePage'
|
||||
import {testAccounts, UserAccountInformation} from '../utils/accountInformation'
|
||||
import { OnboardingPage } from '../pages/onboardingPage'
|
||||
import { SignUpPage } from '../pages/signUpPage'
|
||||
import { ProfilePage } from '../pages/profilePage'
|
||||
import { SettingsPage } from '../pages/settingsPage'
|
||||
|
||||
export const test = base.extend<{
|
||||
authenticatedPage: Page
|
||||
homePage: HomePage
|
||||
onboardingPage: OnboardingPage
|
||||
signUpPage: SignUpPage
|
||||
profilePage: ProfilePage
|
||||
settingsPage: SettingsPage
|
||||
authPage: AuthPage
|
||||
dev_one_account: UserAccountInformation
|
||||
fakerAccount: UserAccountInformation
|
||||
googleAccountOne: UserAccountInformation
|
||||
googleAccountTwo: UserAccountInformation
|
||||
}>({
|
||||
authenticatedPage: async ({page}, use) => {
|
||||
homePage: async ({page}, use) => {
|
||||
const homePage = new HomePage(page)
|
||||
await use(homePage)
|
||||
},
|
||||
onboardingPage: async ({page}, use) => {
|
||||
const onboardingPage = new OnboardingPage(page)
|
||||
await use(onboardingPage)
|
||||
},
|
||||
signUpPage: async ({page}, use) => {
|
||||
const signUpPage = new SignUpPage(page)
|
||||
await use(signUpPage)
|
||||
},
|
||||
profilePage: async ({page}, use) => {
|
||||
const profilePage = new ProfilePage(page)
|
||||
await use(profilePage)
|
||||
},
|
||||
settingsPage: async ({page}, use) => {
|
||||
const settingsPage = new SettingsPage(page)
|
||||
await use(settingsPage)
|
||||
},
|
||||
authPage: async ({page}, use) => {
|
||||
const authPage = new AuthPage(page)
|
||||
|
||||
const email = config.USERS.DEV_1.EMAIL
|
||||
const password = config.USERS.DEV_1.PASSWORD
|
||||
|
||||
try {
|
||||
await seedUser(email, password)
|
||||
} catch (_e) {
|
||||
console.log('User already exists for signinFixture', email)
|
||||
}
|
||||
|
||||
await page.goto('/signin')
|
||||
await authPage.fillEmailField(email)
|
||||
await authPage.fillPasswordField(password)
|
||||
await authPage.clickSignInWithEmailButton()
|
||||
|
||||
await page.waitForURL(/^(?!.*signin).*$/)
|
||||
|
||||
expect(page.url()).not.toContain('/signin')
|
||||
|
||||
await use(page)
|
||||
await use(authPage)
|
||||
},
|
||||
dev_one_account: async ({}, use) => {
|
||||
const account = testAccounts.dev_one_account()
|
||||
await use(account)
|
||||
},
|
||||
fakerAccount: async ({}, use) => {
|
||||
const account = testAccounts.faker_account()
|
||||
await use(account)
|
||||
},
|
||||
googleAccountOne: async ({}, use) => {
|
||||
const account = testAccounts.google_account_one()
|
||||
await use(account)
|
||||
},
|
||||
googleAccountTwo: async ({}, use) => {
|
||||
const account = testAccounts.google_account_two()
|
||||
await use(account)
|
||||
},
|
||||
})
|
||||
|
||||
export {expect} from '@playwright/test'
|
||||
|
||||
@@ -7,7 +7,7 @@ export class AuthPage {
|
||||
private readonly emailField: Locator
|
||||
private readonly passwordField: Locator
|
||||
private readonly signInWithEmailButton: Locator
|
||||
private readonly signInWithGoogleButton: Locator
|
||||
private readonly googleButton: Locator
|
||||
private readonly signUpWithEmailButton: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
@@ -16,7 +16,7 @@ export class AuthPage {
|
||||
this.emailField = page.getByLabel('Email')
|
||||
this.passwordField = page.getByLabel('Password')
|
||||
this.signInWithEmailButton = page.getByRole('button', {name: 'Sign in with Email'})
|
||||
this.signInWithGoogleButton = page.getByRole('button', {name: 'Google'})
|
||||
this.googleButton = page.getByRole('button', {name: 'Google'})
|
||||
this.signUpWithEmailButton = page.getByRole('button', {name: 'Sign up with Email'})
|
||||
}
|
||||
|
||||
@@ -35,9 +35,28 @@ export class AuthPage {
|
||||
await this.signInWithEmailButton.click()
|
||||
}
|
||||
|
||||
async clickSignInWithGoogleButton() {
|
||||
await expect(this.signInWithGoogleButton).toBeVisible()
|
||||
await this.signInWithGoogleButton.click()
|
||||
async clickGoogleButton() {
|
||||
await expect(this.googleButton).toBeVisible()
|
||||
await this.googleButton.click()
|
||||
}
|
||||
|
||||
async getGooglePopupPage(): Promise<Page> {
|
||||
const [popup] = await Promise.all([
|
||||
this.page.context().waitForEvent('page'),
|
||||
this.clickGoogleButton(),
|
||||
])
|
||||
await popup.waitForLoadState()
|
||||
return popup
|
||||
}
|
||||
|
||||
async signInToGoogleAccount(email: string, display_name?: string, username?: string) {
|
||||
const popup = await this.getGooglePopupPage()
|
||||
await popup.getByText('Add new account', {exact: true}).click()
|
||||
await popup.getByLabel('Email').fill(email)
|
||||
if (display_name) await popup.getByLabel('Display name').fill(display_name)
|
||||
if (username) await popup.getByLabel('Screen name', {exact: true}).fill(username)
|
||||
await popup.getByText('Sign in with Google.com', {exact: true}).click()
|
||||
await popup.waitForEvent('close')
|
||||
}
|
||||
|
||||
async clickSignUpWithEmailButton() {
|
||||
|
||||
@@ -2,78 +2,144 @@ import {expect, Locator, Page} from '@playwright/test'
|
||||
import {LocaleTuple} from 'common/constants'
|
||||
|
||||
export class HomePage {
|
||||
private readonly sidebar: Locator
|
||||
private readonly homePageLink: Locator
|
||||
private readonly aboutLink: Locator
|
||||
private readonly faqLink: Locator
|
||||
private readonly voteLink: Locator
|
||||
private readonly eventsLink: Locator
|
||||
private readonly whatsNewLink: Locator
|
||||
private readonly socialsLink: Locator
|
||||
private readonly organizationLink: Locator
|
||||
private readonly contactLink: Locator
|
||||
private readonly profileLink: Locator
|
||||
private readonly signUpButton: Locator
|
||||
private readonly localePicker: Locator
|
||||
private readonly signInLink: Locator
|
||||
private readonly signOutLink: Locator
|
||||
private readonly closeButton: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.homePageLink = page.getByText('Compass', {exact: true})
|
||||
this.aboutLink = page.getByTestId('sidebar-about')
|
||||
this.faqLink = page.getByTestId('sidebar-faq')
|
||||
this.voteLink = page.getByTestId('sidebar-vote')
|
||||
this.eventsLink = page.getByTestId('sidebar-events')
|
||||
this.whatsNewLink = page.getByTestId('sidebar-news')
|
||||
this.socialsLink = page.getByTestId('sidebar-social')
|
||||
this.organizationLink = page.getByTestId('sidebar-organization')
|
||||
this.contactLink = page.getByTestId('sidebar-contact')
|
||||
this.sidebar = page.getByTestId('sidebar')
|
||||
this.homePageLink = page.locator('a[href="/home"]')
|
||||
this.profileLink = page.getByTestId('sidebar-username')
|
||||
this.signUpButton = page.locator('button').filter({hasText: 'Sign up'}).first()
|
||||
this.localePicker = page.getByTestId('sidebar-locale-picker')
|
||||
this.signInLink = page.getByTestId('sidebar-signin')
|
||||
this.signInLink = page.locator('a[href="/signin"]').first()
|
||||
this.signOutLink = page.getByText('Sign out', {exact: true})
|
||||
this.closeButton = page.getByRole('button', {name: 'Close'})
|
||||
}
|
||||
|
||||
async gotToHomePage() {
|
||||
await this.page.goto('/')
|
||||
get sidebarAbout() {
|
||||
return this.sidebar.getByText('About')
|
||||
}
|
||||
|
||||
get sidebarFaq() {
|
||||
return this.sidebar.getByText('FAQ')
|
||||
}
|
||||
|
||||
get sidebarVote() {
|
||||
return this.sidebar.getByText('Vote')
|
||||
}
|
||||
|
||||
get sidebarEvents() {
|
||||
return this.sidebar.getByText('Events')
|
||||
}
|
||||
|
||||
get sidebarWhatsNew() {
|
||||
return this.sidebar.getByText("What's new")
|
||||
}
|
||||
|
||||
get sidebarSocials() {
|
||||
return this.sidebar.getByText('Socials')
|
||||
}
|
||||
|
||||
get sidebarOrganization() {
|
||||
return this.sidebar.getByText('Organization')
|
||||
}
|
||||
|
||||
get sidebarSettings() {
|
||||
return this.sidebar.getByText('Settings')
|
||||
}
|
||||
|
||||
get sidebarPeople() {
|
||||
return this.sidebar.getByText('People')
|
||||
}
|
||||
|
||||
get sidebarNotifs() {
|
||||
return this.sidebar.getByText('Notifs')
|
||||
}
|
||||
|
||||
get sidebarMessages() {
|
||||
return this.sidebar.getByText('Messages')
|
||||
}
|
||||
|
||||
get sidebarContact() {
|
||||
return this.sidebar.getByText('Contact')
|
||||
}
|
||||
|
||||
async goToHomePage() {
|
||||
await this.page.goto('/home')
|
||||
}
|
||||
|
||||
async goToRegisterPage() {
|
||||
await this.page.goto('/register')
|
||||
}
|
||||
|
||||
async goToSigninPage() {
|
||||
await this.page.goto('/signin')
|
||||
}
|
||||
|
||||
async clickAboutLink() {
|
||||
await expect(this.aboutLink).toBeVisible()
|
||||
await this.aboutLink.click()
|
||||
await expect(this.sidebarAbout).toBeVisible()
|
||||
await this.sidebarAbout.click()
|
||||
}
|
||||
|
||||
async clickFaqLink() {
|
||||
await expect(this.faqLink).toBeVisible()
|
||||
await this.faqLink.click()
|
||||
await expect(this.sidebarFaq).toBeVisible()
|
||||
await this.sidebarFaq.click()
|
||||
}
|
||||
|
||||
async clickVoteLink() {
|
||||
await expect(this.voteLink).toBeVisible()
|
||||
await this.voteLink.click()
|
||||
await expect(this.sidebarVote).toBeVisible()
|
||||
await this.sidebarVote.click()
|
||||
}
|
||||
|
||||
async clickEventsLink() {
|
||||
await expect(this.eventsLink).toBeVisible()
|
||||
await this.eventsLink.click()
|
||||
await expect(this.sidebarEvents).toBeVisible()
|
||||
await this.sidebarEvents.click()
|
||||
}
|
||||
|
||||
async clickWhatsNewLink() {
|
||||
await expect(this.whatsNewLink).toBeVisible()
|
||||
await this.whatsNewLink.click()
|
||||
await expect(this.sidebarWhatsNew).toBeVisible()
|
||||
await this.sidebarWhatsNew.click()
|
||||
}
|
||||
|
||||
async clickSocialsLink() {
|
||||
await expect(this.socialsLink).toBeVisible()
|
||||
await this.socialsLink.click()
|
||||
await expect(this.sidebarSocials).toBeVisible()
|
||||
await this.sidebarSocials.click()
|
||||
}
|
||||
|
||||
async clickOrganizationLink() {
|
||||
await expect(this.organizationLink).toBeVisible()
|
||||
await this.organizationLink.click()
|
||||
await expect(this.sidebarOrganization).toBeVisible()
|
||||
await this.sidebarOrganization.click()
|
||||
}
|
||||
|
||||
async clickContactLink() {
|
||||
await expect(this.contactLink).toBeVisible()
|
||||
await this.contactLink.click()
|
||||
await expect(this.sidebarContact).toBeVisible()
|
||||
await this.sidebarContact.click()
|
||||
}
|
||||
|
||||
async clickSettingsLink() {
|
||||
await expect(this.sidebarSettings).toBeVisible()
|
||||
await this.sidebarSettings.click()
|
||||
}
|
||||
|
||||
async clickPeopleLink() {
|
||||
await expect(this.sidebarPeople).toBeVisible()
|
||||
await this.sidebarPeople.click()
|
||||
}
|
||||
|
||||
async clickNotifsLink() {
|
||||
await expect(this.sidebarNotifs).toBeVisible()
|
||||
await this.sidebarNotifs.click()
|
||||
}
|
||||
|
||||
async clickMessagesLink() {
|
||||
await expect(this.sidebarMessages).toBeVisible()
|
||||
await this.sidebarMessages.click()
|
||||
}
|
||||
|
||||
async clickSignUpButton() {
|
||||
@@ -88,7 +154,44 @@ export class HomePage {
|
||||
}
|
||||
|
||||
async clickSignInLink() {
|
||||
await expect(this.sidebar).toBeVisible()
|
||||
await this.sidebar.getByText('Sign in').click()
|
||||
}
|
||||
|
||||
async verifyHomePageLinks() {
|
||||
await expect(this.homePageLink).toBeVisible()
|
||||
await expect(this.sidebarAbout).toBeVisible()
|
||||
await expect(this.sidebarFaq).toBeVisible()
|
||||
await expect(this.sidebarVote).toBeVisible()
|
||||
await expect(this.sidebarEvents).toBeVisible()
|
||||
await expect(this.sidebarWhatsNew).toBeVisible()
|
||||
await expect(this.sidebarSocials).toBeVisible()
|
||||
await expect(this.sidebarOrganization).toBeVisible()
|
||||
await expect(this.sidebarContact).toBeVisible()
|
||||
await expect(this.signUpButton).toBeVisible()
|
||||
await expect(this.signInLink).toBeVisible()
|
||||
await this.signInLink.click()
|
||||
await expect(this.localePicker).toBeVisible()
|
||||
}
|
||||
|
||||
async verifySignedInHomePage(displayName: string) {
|
||||
await expect(this.homePageLink).toBeVisible()
|
||||
await expect(this.profileLink).toBeVisible()
|
||||
await expect(this.profileLink).toContainText(displayName)
|
||||
await expect(this.sidebarPeople).toBeVisible()
|
||||
await expect(this.sidebarNotifs).toBeVisible()
|
||||
await expect(this.sidebarMessages).toBeVisible()
|
||||
await expect(this.sidebarSettings).toBeVisible()
|
||||
await expect(this.sidebarAbout).toBeVisible()
|
||||
await expect(this.sidebarFaq).toBeVisible()
|
||||
await expect(this.sidebarVote).toBeVisible()
|
||||
await expect(this.sidebarEvents).toBeVisible()
|
||||
await expect(this.sidebarWhatsNew).toBeVisible()
|
||||
await expect(this.sidebarSocials).toBeVisible()
|
||||
await expect(this.sidebarOrganization).toBeVisible()
|
||||
await expect(this.sidebarContact).toBeVisible()
|
||||
await expect(this.signOutLink).toBeVisible()
|
||||
await expect(this.signUpButton).not.toBeVisible()
|
||||
await expect(this.signInLink).not.toBeVisible()
|
||||
await expect(this.localePicker).not.toBeVisible()
|
||||
}
|
||||
}
|
||||
|
||||
134
tests/e2e/web/pages/organizationPage.ts
Normal file
134
tests/e2e/web/pages/organizationPage.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import {expect, Locator, Page} from '@playwright/test'
|
||||
|
||||
export class OrganizationPage {
|
||||
private readonly pageTitle: Locator
|
||||
private readonly aboutUsHeading: Locator
|
||||
private readonly proofAndTransparencyHeading: Locator
|
||||
private readonly contactAndSupportHeading: Locator
|
||||
private readonly trustAndLegalHeading: Locator
|
||||
private readonly aboutCompassLink: Locator
|
||||
private readonly constitutionLink: Locator
|
||||
private readonly keyMetricsLink: Locator
|
||||
private readonly pressLink: Locator
|
||||
private readonly financialTransparencyLink: Locator
|
||||
private readonly contactUsLink: Locator
|
||||
private readonly helpAndSupportLink: Locator
|
||||
private readonly securityLink: Locator
|
||||
private readonly termsAndConditionsLink: Locator
|
||||
private readonly privacyPolicyLink: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.pageTitle = page.getByRole('heading', {name: 'Organization'})
|
||||
this.aboutUsHeading = page.getByRole('heading', {name: 'About us'})
|
||||
this.proofAndTransparencyHeading = page.getByRole('heading', {name: 'Proof & transparency'})
|
||||
this.contactAndSupportHeading = page.getByRole('heading', {name: 'Contact & support'})
|
||||
this.trustAndLegalHeading = page.getByRole('heading', {name: 'Trust & legal'})
|
||||
this.aboutCompassLink = page.getByRole('link', {name: 'About Compass'})
|
||||
this.constitutionLink = page.getByRole('link', {name: 'Our constitution'})
|
||||
this.keyMetricsLink = page.getByRole('link', {name: 'Key metrics & growth'})
|
||||
this.pressLink = page.getByRole('link', {name: 'Press'})
|
||||
this.financialTransparencyLink = page.getByRole('link', {name: 'Financial transparency'})
|
||||
this.contactUsLink = page.getByRole('link', {name: 'Contact us'})
|
||||
this.helpAndSupportLink = page.getByRole('link', {name: 'Help & support center'})
|
||||
this.securityLink = page.getByRole('link', {name: 'Security'})
|
||||
this.termsAndConditionsLink = page.getByRole('link', {name: 'Terms and conditions'})
|
||||
this.privacyPolicyLink = page.getByRole('link', {name: 'Privacy policy'})
|
||||
}
|
||||
|
||||
async goToOrganizationPage() {
|
||||
await this.page.goto('/organization')
|
||||
}
|
||||
|
||||
async verifyOrganizationPage() {
|
||||
await expect(this.page).toHaveURL(/\/organization$/)
|
||||
await expect(this.pageTitle).toBeVisible()
|
||||
await expect(this.aboutUsHeading).toBeVisible()
|
||||
await expect(this.proofAndTransparencyHeading).toBeVisible()
|
||||
await expect(this.contactAndSupportHeading).toBeVisible()
|
||||
await expect(this.trustAndLegalHeading).toBeVisible()
|
||||
|
||||
await this.verifyOrganizationLinks()
|
||||
}
|
||||
|
||||
async verifyOrganizationLinks() {
|
||||
await expect(this.aboutCompassLink).toBeVisible()
|
||||
await expect(this.aboutCompassLink).toHaveAttribute('href', '/about')
|
||||
|
||||
await expect(this.constitutionLink).toBeVisible()
|
||||
await expect(this.constitutionLink).toHaveAttribute('href', '/constitution')
|
||||
|
||||
await expect(this.keyMetricsLink).toBeVisible()
|
||||
await expect(this.keyMetricsLink).toHaveAttribute('href', '/stats')
|
||||
|
||||
await expect(this.pressLink).toBeVisible()
|
||||
await expect(this.pressLink).toHaveAttribute('href', '/press')
|
||||
|
||||
await expect(this.financialTransparencyLink).toBeVisible()
|
||||
await expect(this.financialTransparencyLink).toHaveAttribute('href', '/financials')
|
||||
|
||||
await expect(this.contactUsLink).toBeVisible()
|
||||
await expect(this.contactUsLink).toHaveAttribute('href', '/contact')
|
||||
|
||||
await expect(this.helpAndSupportLink).toBeVisible()
|
||||
await expect(this.helpAndSupportLink).toHaveAttribute('href', '/help')
|
||||
|
||||
await expect(this.securityLink).toBeVisible()
|
||||
await expect(this.securityLink).toHaveAttribute('href', '/security')
|
||||
|
||||
await expect(this.termsAndConditionsLink).toBeVisible()
|
||||
await expect(this.termsAndConditionsLink).toHaveAttribute('href', '/terms')
|
||||
|
||||
await expect(this.privacyPolicyLink).toBeVisible()
|
||||
await expect(this.privacyPolicyLink).toHaveAttribute('href', '/privacy')
|
||||
}
|
||||
|
||||
async clickAboutCompassLink() {
|
||||
await expect(this.aboutCompassLink).toBeVisible()
|
||||
await this.aboutCompassLink.click()
|
||||
}
|
||||
|
||||
async clickConstitutionLink() {
|
||||
await expect(this.constitutionLink).toBeVisible()
|
||||
await this.constitutionLink.click()
|
||||
}
|
||||
|
||||
async clickKeyMetricsLink() {
|
||||
await expect(this.keyMetricsLink).toBeVisible()
|
||||
await this.keyMetricsLink.click()
|
||||
}
|
||||
|
||||
async clickPressLink() {
|
||||
await expect(this.pressLink).toBeVisible()
|
||||
await this.pressLink.click()
|
||||
}
|
||||
|
||||
async clickFinancialTransparencyLink() {
|
||||
await expect(this.financialTransparencyLink).toBeVisible()
|
||||
await this.financialTransparencyLink.click()
|
||||
}
|
||||
|
||||
async clickContactUsLink() {
|
||||
await expect(this.contactUsLink).toBeVisible()
|
||||
await this.contactUsLink.click()
|
||||
}
|
||||
|
||||
async clickHelpAndSupportLink() {
|
||||
await expect(this.helpAndSupportLink).toBeVisible()
|
||||
await this.helpAndSupportLink.click()
|
||||
}
|
||||
|
||||
async clickSecurityLink() {
|
||||
await expect(this.securityLink).toBeVisible()
|
||||
await this.securityLink.click()
|
||||
}
|
||||
|
||||
async clickTermsAndConditionsLink() {
|
||||
await expect(this.termsAndConditionsLink).toBeVisible()
|
||||
await this.termsAndConditionsLink.click()
|
||||
}
|
||||
|
||||
async clickPrivacyPolicyLink() {
|
||||
await expect(this.privacyPolicyLink).toBeVisible()
|
||||
await this.privacyPolicyLink.click()
|
||||
}
|
||||
}
|
||||
159
tests/e2e/web/pages/settingsPage.ts
Normal file
159
tests/e2e/web/pages/settingsPage.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import {expect, Locator, Page} from '@playwright/test'
|
||||
import {LocaleTuple} from 'common/constants'
|
||||
import {FontsTuple} from 'web/components/font-picker'
|
||||
|
||||
export class SettingsPage {
|
||||
private readonly localePicker: Locator
|
||||
private readonly measurementSystemToggle: Locator
|
||||
private readonly themeToggle: Locator
|
||||
private readonly fontPicker: Locator
|
||||
private readonly downloadProfileJSONDataButton: Locator
|
||||
private readonly manageHiddenProfilesButton: Locator
|
||||
private readonly hiddenProfilesSection: Locator
|
||||
private readonly directMessagingPreferenceToggle: Locator
|
||||
private readonly privateInterestSignalsToggle: Locator
|
||||
private readonly sendVerificationEmailButton: Locator
|
||||
private readonly verifiedEmailLink: Locator
|
||||
private readonly changeEmailButton: Locator
|
||||
private readonly sendPasswordResetButton: Locator
|
||||
private readonly deleteAccountButton: Locator
|
||||
private readonly closeButton: Locator
|
||||
private readonly cancelButton: Locator
|
||||
private readonly deleteSurveyModal: Locator
|
||||
private readonly deleteSurveyReasons: Locator
|
||||
private readonly deleteSurveyDetails: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.localePicker = page.getByTestId('sidebar-locale-picker')
|
||||
this.measurementSystemToggle = page.getByTestId('measurement-system-toggle')
|
||||
this.themeToggle = page.getByTestId('settings-dark-light-toggle')
|
||||
this.fontPicker = page.getByTestId('settings-font-picker')
|
||||
this.downloadProfileJSONDataButton = page.getByRole('button', {
|
||||
name: 'Download all my data (JSON)',
|
||||
})
|
||||
this.manageHiddenProfilesButton = page.getByRole('button', {name: 'Manage hidden profiles'})
|
||||
this.hiddenProfilesSection = page.getByTestId('hidden-profiles')
|
||||
this.directMessagingPreferenceToggle = page.getByTestId('settings-direct-message-toggle')
|
||||
this.privateInterestSignalsToggle = page.getByTestId('settings-private-interest-signal-toggle')
|
||||
this.sendVerificationEmailButton = page.getByRole('button', {name: 'Send verification email'})
|
||||
this.verifiedEmailLink = page.getByRole('button', {name: 'I verified my email'}) // Need method for this
|
||||
this.changeEmailButton = page.getByRole('button', {name: 'Change email address'})
|
||||
this.sendPasswordResetButton = page.getByRole('button', {name: 'Send password reset email'})
|
||||
this.deleteAccountButton = page.getByRole('button', {name: 'Delete account'})
|
||||
this.closeButton = page.getByRole('button', {name: 'Close'})
|
||||
this.cancelButton = page.getByRole('button', {name: 'Cancel'})
|
||||
this.deleteSurveyModal = page.getByTestId('delete-survey-modal')
|
||||
this.deleteSurveyReasons = page.getByTestId('delete-account-survey-reasons')
|
||||
this.deleteSurveyDetails = page.getByRole('textbox')
|
||||
}
|
||||
|
||||
async setLocale(locale: LocaleTuple) {
|
||||
if (!locale) return
|
||||
await expect(this.localePicker).toBeVisible()
|
||||
await this.localePicker.selectOption(locale[0])
|
||||
}
|
||||
|
||||
async toggleMeasurementSystem() {
|
||||
await expect(this.measurementSystemToggle).toBeVisible()
|
||||
await this.measurementSystemToggle.click()
|
||||
}
|
||||
|
||||
async toggleDisplayTheme() {
|
||||
await expect(this.themeToggle).toBeVisible()
|
||||
await this.themeToggle.click()
|
||||
}
|
||||
|
||||
async setFont(font: FontsTuple) {
|
||||
if (!font) return
|
||||
await expect(this.fontPicker).toBeVisible()
|
||||
await this.fontPicker.selectOption(font[0])
|
||||
}
|
||||
|
||||
async clickdownloadProfileDataButton() {
|
||||
await expect(this.downloadProfileJSONDataButton).toBeVisible()
|
||||
await this.downloadProfileJSONDataButton.click()
|
||||
}
|
||||
|
||||
async clickManageHiddenProfilesButton() {
|
||||
await expect(this.manageHiddenProfilesButton).toBeVisible()
|
||||
await this.manageHiddenProfilesButton.click()
|
||||
}
|
||||
|
||||
async clickCancelButton() {
|
||||
await expect(this.cancelButton).toBeVisible()
|
||||
await this.cancelButton.click()
|
||||
}
|
||||
|
||||
async verifyHiddenProfiles(profiles: string[]) {
|
||||
await expect(this.hiddenProfilesSection).toBeVisible()
|
||||
for (let i = 0; i < profiles.length; i++) {
|
||||
try {
|
||||
await expect(
|
||||
this.hiddenProfilesSection.getByRole('link', {name: `${profiles[i]}`}),
|
||||
).toBeVisible({timeout: 2000})
|
||||
} catch (error) {
|
||||
throw new Error(`Profile ${profiles[i]} has not been hidden`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async unhideProfiles(profile: string) {
|
||||
await expect(this.hiddenProfilesSection).toBeVisible()
|
||||
const hiddenProfiles = await this.hiddenProfilesSection.count()
|
||||
let matchIndex = -1
|
||||
for (let i = 0; i < hiddenProfiles; i++) {
|
||||
const target = await this.hiddenProfilesSection.getByRole('link', {name: `${profile}`})
|
||||
if (target) {
|
||||
matchIndex = i
|
||||
}
|
||||
}
|
||||
await this.hiddenProfilesSection
|
||||
.locator('div')
|
||||
.nth(matchIndex)
|
||||
.getByRole('button', {name: 'Unhide'})
|
||||
.click()
|
||||
}
|
||||
|
||||
async toggleDirectMessagingPreferences() {
|
||||
await expect(this.directMessagingPreferenceToggle).toBeVisible()
|
||||
await this.directMessagingPreferenceToggle.click()
|
||||
}
|
||||
|
||||
async togglePrivateInterestSignalsPreferences() {
|
||||
await expect(this.privateInterestSignalsToggle).toBeVisible()
|
||||
await this.privateInterestSignalsToggle.click()
|
||||
}
|
||||
|
||||
async clickSendVerificationEmailButton() {
|
||||
await expect(this.sendVerificationEmailButton).toBeVisible()
|
||||
await this.sendVerificationEmailButton.click()
|
||||
}
|
||||
|
||||
async clickChangeEmailAddressButton() {
|
||||
await expect(this.changeEmailButton).toBeVisible()
|
||||
await this.changeEmailButton.click()
|
||||
}
|
||||
|
||||
async clickSendPasswordResetEmailButton() {
|
||||
await expect(this.sendPasswordResetButton).toBeVisible()
|
||||
await this.sendPasswordResetButton.click()
|
||||
}
|
||||
|
||||
async clickDeleteAccountButton() {
|
||||
await expect(this.deleteAccountButton).toBeVisible()
|
||||
await this.deleteAccountButton.click()
|
||||
}
|
||||
|
||||
async clickCloseButton() {
|
||||
await expect(this.closeButton).toBeVisible()
|
||||
await this.closeButton.click()
|
||||
}
|
||||
|
||||
async fillDeleteAccountSurvey(reason: string) {
|
||||
await expect(this.deleteSurveyModal).toBeVisible()
|
||||
await expect(this.deleteSurveyReasons).toBeVisible()
|
||||
await this.deleteSurveyReasons.locator('div').nth(1).click()
|
||||
await expect(this.deleteSurveyDetails).toBeVisible()
|
||||
await this.deleteSurveyDetails.fill(reason)
|
||||
}
|
||||
}
|
||||
113
tests/e2e/web/pages/socialPage.ts
Normal file
113
tests/e2e/web/pages/socialPage.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import {expect, Locator, Page} from '@playwright/test'
|
||||
import {
|
||||
discordLink,
|
||||
githubRepo,
|
||||
instagramLink,
|
||||
redditLink,
|
||||
stoatLink,
|
||||
supportEmail,
|
||||
xLink,
|
||||
} from 'common/constants'
|
||||
|
||||
export class SocialPage {
|
||||
private readonly pageTitle: Locator
|
||||
private readonly communityHeading: Locator
|
||||
private readonly followAndUpdatesHeading: Locator
|
||||
private readonly developmentHeading: Locator
|
||||
private readonly contactHeading: Locator
|
||||
private readonly discordButton: Locator
|
||||
private readonly redditButton: Locator
|
||||
private readonly stoatButton: Locator
|
||||
private readonly xButton: Locator
|
||||
private readonly instagramButton: Locator
|
||||
private readonly githubButton: Locator
|
||||
private readonly emailButton: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.pageTitle = page.getByRole('heading', {name: 'Socials'})
|
||||
this.communityHeading = page.getByRole('heading', {name: 'Community'})
|
||||
this.followAndUpdatesHeading = page.getByRole('heading', {name: 'Follow & Updates'})
|
||||
this.developmentHeading = page.getByRole('heading', {name: 'Development'})
|
||||
this.contactHeading = page.getByRole('heading', {name: 'Contact'})
|
||||
this.discordButton = page.getByRole('link', {name: 'Discord'})
|
||||
this.redditButton = page.getByRole('link', {name: 'Reddit'})
|
||||
this.stoatButton = page.getByRole('link', {name: 'Revolt / Stoat'})
|
||||
this.xButton = page.getByRole('link', {name: 'X'})
|
||||
this.instagramButton = page.getByRole('link', {name: 'Instagram'})
|
||||
this.githubButton = page.getByRole('link', {name: 'GitHub'})
|
||||
this.emailButton = page.getByRole('link', {name: `Email ${supportEmail}`})
|
||||
}
|
||||
|
||||
async goToSocialPage() {
|
||||
await this.page.goto('/social')
|
||||
}
|
||||
|
||||
async verifySocialPage() {
|
||||
await expect(this.page).toHaveURL(/\/social$/)
|
||||
await expect(this.pageTitle).toBeVisible()
|
||||
await expect(this.communityHeading).toBeVisible()
|
||||
await expect(this.followAndUpdatesHeading).toBeVisible()
|
||||
await expect(this.developmentHeading).toBeVisible()
|
||||
await expect(this.contactHeading).toBeVisible()
|
||||
|
||||
await this.verifySocialLinks()
|
||||
}
|
||||
|
||||
async verifySocialLinks() {
|
||||
await expect(this.discordButton).toBeVisible()
|
||||
await expect(this.discordButton).toHaveAttribute('href', discordLink)
|
||||
|
||||
await expect(this.redditButton).toBeVisible()
|
||||
await expect(this.redditButton).toHaveAttribute('href', redditLink)
|
||||
|
||||
await expect(this.stoatButton).toBeVisible()
|
||||
await expect(this.stoatButton).toHaveAttribute('href', stoatLink)
|
||||
|
||||
await expect(this.xButton).toBeVisible()
|
||||
await expect(this.xButton).toHaveAttribute('href', xLink)
|
||||
|
||||
await expect(this.instagramButton).toBeVisible()
|
||||
await expect(this.instagramButton).toHaveAttribute('href', instagramLink)
|
||||
|
||||
await expect(this.githubButton).toBeVisible()
|
||||
await expect(this.githubButton).toHaveAttribute('href', githubRepo)
|
||||
|
||||
await expect(this.emailButton).toBeVisible()
|
||||
await expect(this.emailButton).toHaveAttribute('href', `mailto:${supportEmail}`)
|
||||
}
|
||||
|
||||
async clickDiscordButton() {
|
||||
await expect(this.discordButton).toBeVisible()
|
||||
await this.discordButton.click()
|
||||
}
|
||||
|
||||
async clickRedditButton() {
|
||||
await expect(this.redditButton).toBeVisible()
|
||||
await this.redditButton.click()
|
||||
}
|
||||
|
||||
async clickStoatButton() {
|
||||
await expect(this.stoatButton).toBeVisible()
|
||||
await this.stoatButton.click()
|
||||
}
|
||||
|
||||
async clickXButton() {
|
||||
await expect(this.xButton).toBeVisible()
|
||||
await this.xButton.click()
|
||||
}
|
||||
|
||||
async clickInstagramButton() {
|
||||
await expect(this.instagramButton).toBeVisible()
|
||||
await this.instagramButton.click()
|
||||
}
|
||||
|
||||
async clickGitHubButton() {
|
||||
await expect(this.githubButton).toBeVisible()
|
||||
await this.githubButton.click()
|
||||
}
|
||||
|
||||
async clickEmailButton() {
|
||||
await expect(this.emailButton).toBeVisible()
|
||||
await this.emailButton.click()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import {userInformationFromDb} from '../../utils/databaseUtils'
|
||||
import {progressToRequiredForm} from '../utils/testCleanupHelpers'
|
||||
import {expect, test} from '../fixtures/base'
|
||||
import {registerWithEmail, skipOnboardingHeadToProfile} from '../utils/testCleanupHelpers'
|
||||
|
||||
test.describe('when given valid input', () => {
|
||||
test('should successfully complete the onboarding flow', async ({
|
||||
test('should successfully complete the onboarding flow with email', async ({
|
||||
homePage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
@@ -12,13 +12,9 @@ test.describe('when given valid input', () => {
|
||||
onboardingAccount,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should successfully complete the onboarding flow" with ${onboardingAccount.username}`,
|
||||
`Starting "should successfully complete the onboarding flow with email" with ${onboardingAccount.username}`,
|
||||
)
|
||||
await homePage.gotToHomePage()
|
||||
await homePage.clickSignUpButton()
|
||||
await authPage.fillEmailField(onboardingAccount.email)
|
||||
await authPage.fillPasswordField(onboardingAccount.password)
|
||||
await authPage.clickSignUpWithEmailButton()
|
||||
await registerWithEmail(homePage, authPage, onboardingAccount)
|
||||
await onboardingPage.clickContinueButton() //First continue
|
||||
await onboardingPage.clickContinueButton() //Second continue
|
||||
await onboardingPage.clickGetStartedButton()
|
||||
@@ -225,6 +221,38 @@ test.describe('when given valid input', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('should successfully complete the onboarding flow with google account', async ({
|
||||
homePage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
authPage,
|
||||
profilePage,
|
||||
googleAccountOne,
|
||||
headless,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should successfully complete the onboarding flow with google account" with ${googleAccountOne.username}`,
|
||||
)
|
||||
test.skip(headless, 'Google popup auth test requires headed mode')
|
||||
await homePage.goToRegisterPage()
|
||||
await authPage.fillPasswordField('') //The test only passes when this is added...something is weird here
|
||||
await authPage.signInToGoogleAccount(
|
||||
googleAccountOne.email,
|
||||
googleAccountOne.display_name,
|
||||
googleAccountOne.username,
|
||||
)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, googleAccountOne)
|
||||
|
||||
//Verify displayed information is correct
|
||||
await profilePage.verifyDisplayName(googleAccountOne.display_name)
|
||||
|
||||
//Verify database info
|
||||
const dbInfo = await userInformationFromDb(googleAccountOne)
|
||||
|
||||
await expect(dbInfo.user.name).toContain(googleAccountOne.display_name)
|
||||
await expect(dbInfo.user.username).toContain(googleAccountOne.username)
|
||||
})
|
||||
|
||||
test('should successfully skip the onboarding flow', async ({
|
||||
homePage,
|
||||
onboardingPage,
|
||||
@@ -236,13 +264,8 @@ test.describe('when given valid input', () => {
|
||||
console.log(
|
||||
`Starting "should successfully skip the onboarding flow" with ${fakerAccount.username}`,
|
||||
)
|
||||
await progressToRequiredForm(homePage, authPage, fakerAccount, onboardingPage)
|
||||
await signUpPage.fillDisplayName(fakerAccount.display_name)
|
||||
await signUpPage.fillUsername(fakerAccount.username)
|
||||
await signUpPage.clickNextButton()
|
||||
await signUpPage.clickNextButton() //Skip optional information
|
||||
await profilePage.clickCloseButton()
|
||||
await onboardingPage.clickRefineProfileButton()
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, fakerAccount)
|
||||
|
||||
//Verify displayed information is correct
|
||||
await profilePage.verifyDisplayName(fakerAccount.display_name)
|
||||
@@ -265,13 +288,8 @@ test.describe('when given valid input', () => {
|
||||
console.log(
|
||||
`Starting "should successfully enter optional information after completing flow" with ${fakerAccount.username}`,
|
||||
)
|
||||
await progressToRequiredForm(homePage, authPage, fakerAccount, onboardingPage)
|
||||
await signUpPage.fillDisplayName(fakerAccount.display_name)
|
||||
await signUpPage.fillUsername(fakerAccount.username)
|
||||
await signUpPage.clickNextButton()
|
||||
await signUpPage.clickNextButton() //Skip optional information
|
||||
await profilePage.clickCloseButton()
|
||||
await onboardingPage.clickRefineProfileButton()
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, fakerAccount)
|
||||
await profilePage.clickEditProfileButton()
|
||||
await signUpPage.chooseGender(fakerAccount.gender)
|
||||
await signUpPage.fillAge(fakerAccount.age)
|
||||
@@ -313,7 +331,8 @@ test.describe('when given valid input', () => {
|
||||
console.log(
|
||||
`Starting "should successfully use the start answering option" with ${fakerAccount.username}`,
|
||||
)
|
||||
await progressToRequiredForm(homePage, authPage, fakerAccount, onboardingPage)
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await onboardingPage.clickSkipOnboardingButton()
|
||||
await signUpPage.fillDisplayName(fakerAccount.display_name)
|
||||
await signUpPage.fillUsername(fakerAccount.username)
|
||||
await signUpPage.clickNextButton()
|
||||
@@ -338,22 +357,16 @@ test.describe('when given valid input', () => {
|
||||
})
|
||||
|
||||
test.describe('should successfully complete the onboarding flow after using the back button', () => {
|
||||
test.beforeEach(async ({homePage, authPage, fakerAccount}) => {
|
||||
console.log(`Before each with ${fakerAccount.username}`)
|
||||
await homePage.gotToHomePage()
|
||||
await homePage.clickSignUpButton()
|
||||
await authPage.fillEmailField(fakerAccount.email)
|
||||
await authPage.fillPasswordField(fakerAccount.password)
|
||||
await authPage.clickSignUpWithEmailButton()
|
||||
})
|
||||
|
||||
test("the first time it's an option", async ({
|
||||
homePage,
|
||||
authPage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
profilePage,
|
||||
fakerAccount,
|
||||
}) => {
|
||||
console.log(`Starting "the first time its an option" with ${fakerAccount.username}`)
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await onboardingPage.clickContinueButton()
|
||||
await onboardingPage.clickBackButton()
|
||||
await onboardingPage.clickContinueButton()
|
||||
@@ -377,12 +390,15 @@ test.describe('when given valid input', () => {
|
||||
})
|
||||
|
||||
test("the second time it's an option", async ({
|
||||
homePage,
|
||||
authPage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
profilePage,
|
||||
fakerAccount,
|
||||
}) => {
|
||||
console.log(`Starting "the second time its an option" with ${fakerAccount.username}`)
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await onboardingPage.clickContinueButton()
|
||||
await onboardingPage.clickContinueButton()
|
||||
await onboardingPage.clickBackButton()
|
||||
@@ -407,6 +423,6 @@ test.describe('when given valid input', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('when an error occurs', () => {
|
||||
test('placeholder', async () => {})
|
||||
})
|
||||
// test.describe('when an error occurs', () => {
|
||||
// test('placeholder', async ({}) => {})
|
||||
// })
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import {expect} from '@playwright/test'
|
||||
|
||||
import {test} from '../fixtures/signInFixture'
|
||||
|
||||
test('should be logged in and see settings page', async ({authenticatedPage}) => {
|
||||
await authenticatedPage.goto('/settings')
|
||||
|
||||
await expect(authenticatedPage.getByRole('heading', {name: 'Theme'})).toBeVisible()
|
||||
})
|
||||
120
tests/e2e/web/specs/signIn.spec.ts
Normal file
120
tests/e2e/web/specs/signIn.spec.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { userInformationFromDb } from '../../utils/databaseUtils'
|
||||
import {seedUser} from '../../utils/seedDatabase'
|
||||
import {expect, test} from '../fixtures/signInFixture'
|
||||
import {testAccounts} from '../utils/accountInformation'
|
||||
import {
|
||||
deleteProfileFromSettings,
|
||||
registerWithEmail,
|
||||
signinWithEmail,
|
||||
skipOnboardingHeadToProfile
|
||||
} from '../utils/testCleanupHelpers'
|
||||
|
||||
//Seed the account
|
||||
test.beforeAll(async () => {
|
||||
const dev_1_Account = testAccounts.dev_one_account()
|
||||
try {
|
||||
await seedUser(
|
||||
dev_1_Account.email,
|
||||
dev_1_Account.password,
|
||||
undefined,
|
||||
dev_1_Account.display_name,
|
||||
dev_1_Account.username,
|
||||
)
|
||||
} catch (_e) {
|
||||
console.log('User already exists for signinFixture', dev_1_Account.email)
|
||||
}
|
||||
})
|
||||
|
||||
test.describe('when given valid input', () => {
|
||||
test('should be able to sign in to an available account', async ({
|
||||
homePage,
|
||||
authPage,
|
||||
dev_one_account,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should be able to sign in to an available account" with ${dev_one_account.username}`,
|
||||
)
|
||||
await signinWithEmail(homePage, authPage, dev_one_account)
|
||||
await homePage.goToHomePage()
|
||||
await homePage.verifySignedInHomePage(dev_one_account.display_name)
|
||||
})
|
||||
|
||||
test('should successfully delete an account created via email and password', async ({
|
||||
homePage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
authPage,
|
||||
profilePage,
|
||||
settingsPage,
|
||||
fakerAccount,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should successfully delete an account created via email and password" with ${fakerAccount.username}`,
|
||||
)
|
||||
await registerWithEmail(homePage, authPage, fakerAccount)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, fakerAccount)
|
||||
|
||||
//Verify displayed information is correct
|
||||
await profilePage.verifyDisplayName(fakerAccount.display_name)
|
||||
|
||||
//Verify database info
|
||||
const dbInfo = await userInformationFromDb(fakerAccount)
|
||||
|
||||
await expect(dbInfo.user.name).toContain(fakerAccount.display_name)
|
||||
await expect(dbInfo.user.username).toContain(fakerAccount.username)
|
||||
|
||||
await deleteProfileFromSettings(homePage, settingsPage)
|
||||
})
|
||||
|
||||
test('should successfully delete an account created via google auth', async ({
|
||||
homePage,
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
authPage,
|
||||
profilePage,
|
||||
settingsPage,
|
||||
googleAccountTwo,
|
||||
headless,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should successfully delete an account created via google auth" with ${googleAccountTwo.username}`,
|
||||
)
|
||||
test.skip(headless, 'Google popup auth test requires headed mode')
|
||||
await homePage.goToRegisterPage()
|
||||
await authPage.fillPasswordField('') //The test only passes when this is added...something is weird here
|
||||
await authPage.signInToGoogleAccount(
|
||||
googleAccountTwo.email,
|
||||
googleAccountTwo.display_name,
|
||||
googleAccountTwo.username,
|
||||
)
|
||||
await skipOnboardingHeadToProfile(onboardingPage, signUpPage, profilePage, googleAccountTwo)
|
||||
|
||||
//Verify displayed information is correct
|
||||
await profilePage.verifyDisplayName(googleAccountTwo.display_name)
|
||||
|
||||
//Verify database info
|
||||
const dbInfo = await userInformationFromDb(googleAccountTwo)
|
||||
|
||||
await expect(dbInfo.user.name).toContain(googleAccountTwo.display_name)
|
||||
await expect(dbInfo.user.username).toContain(googleAccountTwo.username)
|
||||
|
||||
await deleteProfileFromSettings(homePage, settingsPage)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('when given invalid input', () => {
|
||||
test('should not be able to sign in to an available account', async ({
|
||||
homePage,
|
||||
authPage,
|
||||
dev_one_account,
|
||||
page,
|
||||
}) => {
|
||||
console.log(
|
||||
`Starting "should not be able to sign in to an available account" with ${dev_one_account.username}`,
|
||||
)
|
||||
await signinWithEmail(homePage, authPage, dev_one_account.email, 'ThisPassword')
|
||||
await expect(
|
||||
page.getByText('Failed to sign in with your email and password', {exact: true}),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import {expect, test} from '../fixtures/base'
|
||||
import {progressToRequiredForm} from '../utils/testCleanupHelpers'
|
||||
import {registerWithEmail} from '../utils/testCleanupHelpers'
|
||||
|
||||
test.describe('when given valid input', () => {
|
||||
test('placeholder', async () => {})
|
||||
@@ -13,7 +13,11 @@ test.describe('when an error occurs', () => {
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
}) => {
|
||||
await progressToRequiredForm(homePage, authPage, specAccount, onboardingPage)
|
||||
console.log(
|
||||
`Starting "should disable the button "Next" when the display name field is empty" with ${specAccount.username}`,
|
||||
)
|
||||
await registerWithEmail(homePage, authPage, specAccount)
|
||||
await onboardingPage.clickSkipOnboardingButton()
|
||||
await signUpPage.fillDisplayName('')
|
||||
await signUpPage.fillUsername(specAccount.username)
|
||||
await signUpPage.verifyDisplayNameError()
|
||||
@@ -27,7 +31,11 @@ test.describe('when an error occurs', () => {
|
||||
onboardingPage,
|
||||
signUpPage,
|
||||
}) => {
|
||||
await progressToRequiredForm(homePage, authPage, specAccount, onboardingPage)
|
||||
console.log(
|
||||
`Starting "should disable the button "Next" when the username field is empty" with ${specAccount.username}`,
|
||||
)
|
||||
await registerWithEmail(homePage, authPage, specAccount)
|
||||
await onboardingPage.clickSkipOnboardingButton()
|
||||
await signUpPage.fillDisplayName(specAccount.display_name)
|
||||
await signUpPage.fillUsername('')
|
||||
await signUpPage.verifyUsernameError()
|
||||
|
||||
@@ -102,7 +102,9 @@ type AccountConfig = {
|
||||
spec_account: () => UserAccountInformation
|
||||
dev_one_account: () => UserAccountInformation
|
||||
dev_two_account: () => UserAccountInformation
|
||||
account_all_info: () => UserAccountInformation
|
||||
google_account_one: () => UserAccountInformation
|
||||
google_account_two: () => UserAccountInformation
|
||||
email_account_all_info: () => UserAccountInformation
|
||||
}
|
||||
|
||||
export const testAccounts: AccountConfig = {
|
||||
@@ -137,10 +139,10 @@ export const testAccounts: AccountConfig = {
|
||||
dev_one_account: () => {
|
||||
const id = crypto.randomUUID().slice(0, 6)
|
||||
return {
|
||||
email: `dev_1_${id}@compass.com`,
|
||||
email: `dev_1@compass.com`,
|
||||
password: 'dev_1Password',
|
||||
display_name: 'Dev1.Compass',
|
||||
username: `Dev1.Connections_${id}`,
|
||||
username: `Dev1.Connections`,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -150,11 +152,31 @@ export const testAccounts: AccountConfig = {
|
||||
email: 'dev_2@compass.com',
|
||||
password: 'dev_2Password',
|
||||
display_name: 'Dev2.Compass',
|
||||
username: `Dev2.Connections_${id}`,
|
||||
username: `Dev2.Connections_`,
|
||||
}
|
||||
},
|
||||
|
||||
account_all_info: () => {
|
||||
google_account_one: () => {
|
||||
const id = crypto.randomUUID().slice(0, 6)
|
||||
return {
|
||||
email: `g1_compass_${id}@gmail.com`,
|
||||
password: 'G_oneCompassTest',
|
||||
display_name: 'Google_one_Compass',
|
||||
username: `G1_Connect_${id}`,
|
||||
}
|
||||
},
|
||||
|
||||
google_account_two: () => {
|
||||
const id = crypto.randomUUID().slice(0, 6)
|
||||
return {
|
||||
email: `g2_compass_${id}@gmail.com`,
|
||||
password: 'G_twoCompassTest',
|
||||
display_name: 'Google_two_Compass',
|
||||
username: `G2_Connect_${id}`,
|
||||
}
|
||||
},
|
||||
|
||||
email_account_all_info: () => {
|
||||
const id = crypto.randomUUID().slice(0, 6)
|
||||
return {
|
||||
// Use a non-real TLD like @test.compass to make it obvious these are test accounts and prevent accidental emails
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import {deleteFromDb} from '../../utils/databaseUtils'
|
||||
import {deleteAccount, firebaseLogin} from '../../utils/firebaseUtils'
|
||||
import {deleteAccount, firebaseLoginEmailPassword} from '../../utils/firebaseUtils'
|
||||
import {UserAccountInformation} from './accountInformation'
|
||||
import {AuthObject} from './networkUtils'
|
||||
|
||||
export async function deleteUser(email: string, password: string) {
|
||||
type AuthType = 'Email/Password' | 'Google'
|
||||
export async function deleteUser(
|
||||
authType: AuthType,
|
||||
account?: UserAccountInformation,
|
||||
authInfo?: AuthObject,
|
||||
) {
|
||||
try {
|
||||
const loginInfo = await firebaseLogin(email, password)
|
||||
await deleteAccount(loginInfo)
|
||||
await deleteFromDb(loginInfo.data.localId)
|
||||
let loginInfo
|
||||
if (authType === 'Email/Password') {
|
||||
loginInfo = await firebaseLoginEmailPassword(account?.email, account?.password)
|
||||
await deleteAccount(loginInfo?.data.idToken)
|
||||
await deleteFromDb(loginInfo?.data.localId)
|
||||
} else if (authType === 'Google' && authInfo) {
|
||||
await deleteAccount(authInfo.idToken)
|
||||
await deleteFromDb(authInfo.localId)
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Skip deletion if user doesn't exist or other auth errors occur
|
||||
if (
|
||||
err.response?.status === 400 ||
|
||||
err.response?.data?.error?.message?.includes('EMAIL_NOT_FOUND')
|
||||
) {
|
||||
console.log(`Email not found, skipping user deletion for ${email}`)
|
||||
console.log(`Email not found, skipping user deletion for ${account?.email}`)
|
||||
return
|
||||
}
|
||||
console.log(err)
|
||||
|
||||
25
tests/e2e/web/utils/networkUtils.ts
Normal file
25
tests/e2e/web/utils/networkUtils.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Page} from '@playwright/test'
|
||||
|
||||
export type AuthObject = {
|
||||
idToken: string
|
||||
localId: string
|
||||
}
|
||||
|
||||
export async function getAuthAccountInfo(page: Page): Promise<() => AuthObject> {
|
||||
let accountIdTokenAndLocalId: AuthObject | undefined
|
||||
|
||||
await page.route('**/accounts:signInWithIdp**', async (route) => {
|
||||
const response = await route.fetch()
|
||||
const body = await response.json()
|
||||
accountIdTokenAndLocalId = {idToken: body.idToken, localId: body.localId}
|
||||
await route.fulfill({response})
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (!accountIdTokenAndLocalId) {
|
||||
console.log('Sign-in was never intercepted — test may have been skipped')
|
||||
return undefined as unknown as AuthObject
|
||||
}
|
||||
return accountIdTokenAndLocalId
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,64 @@
|
||||
import {AuthPage} from '../pages/AuthPage'
|
||||
import {HomePage} from '../pages/homePage'
|
||||
import {OnboardingPage} from '../pages/onboardingPage'
|
||||
import { OnboardingPage } from '../pages/onboardingPage'
|
||||
import { ProfilePage } from '../pages/profilePage'
|
||||
import { SettingsPage } from '../pages/settingsPage'
|
||||
import { SignUpPage } from '../pages/signUpPage'
|
||||
import {UserAccountInformation} from '../utils/accountInformation'
|
||||
|
||||
export async function progressToRequiredForm(
|
||||
export async function registerWithEmail(
|
||||
homePage: HomePage,
|
||||
authPage: AuthPage,
|
||||
account: UserAccountInformation,
|
||||
onboardingPage: OnboardingPage,
|
||||
) {
|
||||
await homePage.gotToHomePage()
|
||||
await homePage.clickSignUpButton()
|
||||
await homePage.goToRegisterPage()
|
||||
await authPage.fillEmailField(account.email)
|
||||
await authPage.fillPasswordField(account.password)
|
||||
await authPage.clickSignUpWithEmailButton()
|
||||
await onboardingPage.clickSkipOnboardingButton()
|
||||
}
|
||||
|
||||
export async function signinWithEmail(
|
||||
homePage: HomePage,
|
||||
authPage: AuthPage,
|
||||
accountOrEmail: UserAccountInformation | string,
|
||||
password?: string,
|
||||
) {
|
||||
const email = typeof accountOrEmail === 'string' ? accountOrEmail : accountOrEmail.email
|
||||
|
||||
const resolvedPassword = typeof accountOrEmail === 'string' ? password : accountOrEmail.password
|
||||
|
||||
if (!email || !resolvedPassword) {
|
||||
throw new Error('Provide either an `account` or `email` and `password`.')
|
||||
}
|
||||
|
||||
await homePage.goToSigninPage()
|
||||
await authPage.fillEmailField(email)
|
||||
await authPage.fillPasswordField(resolvedPassword)
|
||||
await authPage.clickSignInWithEmailButton()
|
||||
}
|
||||
|
||||
export async function skipOnboardingHeadToProfile(
|
||||
onboardingPage: OnboardingPage,
|
||||
signUpPage: SignUpPage,
|
||||
profilePage: ProfilePage,
|
||||
account: UserAccountInformation,
|
||||
) {
|
||||
await onboardingPage.clickSkipOnboardingButton()
|
||||
await signUpPage.fillDisplayName(account.display_name)
|
||||
await signUpPage.fillUsername(account.username)
|
||||
await signUpPage.clickNextButton()
|
||||
await signUpPage.clickNextButton()
|
||||
await profilePage.clickCloseButton()
|
||||
await onboardingPage.clickRefineProfileButton()
|
||||
}
|
||||
|
||||
export async function deleteProfileFromSettings(
|
||||
homePage: HomePage,
|
||||
settingsPage: SettingsPage,
|
||||
) {
|
||||
await homePage.clickSettingsLink()
|
||||
await settingsPage.clickDeleteAccountButton()
|
||||
await settingsPage.fillDeleteAccountSurvey('Delete me')
|
||||
await settingsPage.clickDeleteAccountButton()
|
||||
await homePage.verifyHomePageLinks()
|
||||
}
|
||||
@@ -67,6 +67,7 @@ export function ConnectionPreferencesSettings() {
|
||||
</div>
|
||||
</div>
|
||||
<SwitchSetting
|
||||
testId="settings-direct-message-toggle"
|
||||
checked={allowDirectMessaging}
|
||||
onChange={handleDirectMessagingChange}
|
||||
disabled={isUpdating}
|
||||
@@ -87,6 +88,7 @@ export function ConnectionPreferencesSettings() {
|
||||
</div>
|
||||
</div>
|
||||
<SwitchSetting
|
||||
testId="settings-private-interest-signal-toggle"
|
||||
checked={allowInterestIndicating}
|
||||
onChange={handleInterestIndicatingChange}
|
||||
disabled={isUpdating}
|
||||
|
||||
@@ -19,6 +19,7 @@ export function FontPicker(props: {className?: string} = {}) {
|
||||
|
||||
return (
|
||||
<select
|
||||
data-testid="settings-font-picker"
|
||||
id="font-picker"
|
||||
value={font}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setFont(e.target.value as FontOption)}
|
||||
@@ -35,3 +36,8 @@ export function FontPicker(props: {className?: string} = {}) {
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
//Exported types for test files to use when referencing the keys of the choices objects
|
||||
export type FontsTuple = {
|
||||
[K in keyof typeof EN_TRANSLATIONS]: [K, (typeof EN_TRANSLATIONS)[K]]
|
||||
}[keyof typeof EN_TRANSLATIONS]
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function MeasurementSystemToggle(props: {className?: string}) {
|
||||
</span>
|
||||
|
||||
<Switch
|
||||
data-testid="measurement-system-toggle"
|
||||
checked={isEnabled}
|
||||
onChange={(enabled: boolean) => setMeasurementSystem(enabled ? 'metric' : 'imperial')}
|
||||
className={clsx(
|
||||
|
||||
@@ -22,7 +22,7 @@ export function ProfileSummary(props: {user: User; className?: string}) {
|
||||
<div className="w-2 shrink" />
|
||||
<Avatar avatarUrl={profile?.pinned_url ?? ''} username={user.username} noLink />
|
||||
<div className="mr-1 w-2 shrink-[2]" />
|
||||
<div className="shrink-0 grow">
|
||||
<div className="shrink-0 grow" data-testid="sidebar-username">
|
||||
<div className="group-hover:text-primary-700">{user.name}</div>
|
||||
</div>
|
||||
<div className="w-2 shrink" />
|
||||
|
||||
@@ -54,7 +54,6 @@ export function SidebarItem(props: {item: Item; currentPage?: string}) {
|
||||
return (
|
||||
<Link
|
||||
href={item.href}
|
||||
data-testid={`sidebar-${item.href?.replace('/', '')}`}
|
||||
aria-current={isCurrentPage ? 'page' : undefined}
|
||||
onClick={onClick}
|
||||
className={sidebarClass}
|
||||
|
||||
@@ -41,6 +41,7 @@ export default function Sidebar(props: {
|
||||
<nav
|
||||
id="main-navigation"
|
||||
aria-label="Sidebar"
|
||||
data-testid="sidebar"
|
||||
className={clsx(
|
||||
'flex flex-col h-[calc(100dvh-var(--hloss))] mb-[calc(var(--bnh))] mt-[calc(var(--tnh))]',
|
||||
className,
|
||||
@@ -119,7 +120,6 @@ export const SignUpButton = (props: {
|
||||
|
||||
return (
|
||||
<Button
|
||||
data-testid="side-bar-sign-up-button"
|
||||
color={color ?? 'gradient'}
|
||||
size={size ?? 'xl'}
|
||||
onClick={startSignup}
|
||||
|
||||
@@ -141,7 +141,7 @@ export function DeleteAccountSurveyModal() {
|
||||
onSubmitWithSuccess={handleDeleteAccount}
|
||||
disabled={false}
|
||||
>
|
||||
<Col className="gap-4">
|
||||
<Col className="gap-4" data-testid="delete-survey-modal">
|
||||
<Title>{t('delete_survey.title', 'Sorry to see you go')}</Title>
|
||||
|
||||
<div>
|
||||
@@ -157,7 +157,7 @@ export function DeleteAccountSurveyModal() {
|
||||
{t('delete_survey.reason_label', 'Why are you deleting your account?')}
|
||||
</RadioGroup.Label>
|
||||
|
||||
<div className="space-y-2 mt-2">
|
||||
<div className="space-y-2 mt-2" data-testid="delete-account-survey-reasons">
|
||||
{Object.entries(reasonsMap).map(([key, value]) => (
|
||||
<RadioGroup.Option
|
||||
key={key}
|
||||
|
||||
@@ -49,7 +49,10 @@ export function HiddenProfilesModal(props: {open: boolean; setOpen: (open: boole
|
||||
{t('settings.hidden_profiles.title', "Profiles you've hidden")}
|
||||
</Title>
|
||||
{hiddenProfiles && hiddenProfiles.length > 0 && (
|
||||
<Col className={clsx('divide-y divide-canvas-300 w-full pr-4', SCROLLABLE_MODAL_CLASS)}>
|
||||
<Col
|
||||
className={clsx('divide-y divide-canvas-300 w-full pr-4', SCROLLABLE_MODAL_CLASS)}
|
||||
data-testid="hidden-profiles"
|
||||
>
|
||||
{hiddenProfiles.map((u) => (
|
||||
<Row key={u.id} className="items-center justify-between py-2 gap-2">
|
||||
<Link className="w-full rounded-md hover:bg-canvas-100 p-2" href={'/' + u.username}>
|
||||
|
||||
@@ -9,10 +9,11 @@ export const SwitchSetting = (props: {
|
||||
label?: 'Web' | 'Email' | 'Mobile'
|
||||
disabled: boolean
|
||||
colorMode?: ToggleColorMode
|
||||
testId?: string
|
||||
}) => {
|
||||
const {colorMode, checked, onChange, label, disabled} = props
|
||||
const {colorMode, checked, onChange, label, disabled, testId} = props
|
||||
return (
|
||||
<Switch.Group as="div" className="flex items-center gap-3">
|
||||
<Switch.Group as="div" className="flex items-center gap-3" data-testid={testId}>
|
||||
<ShortToggle colorMode={colorMode} on={checked} setOn={onChange} disabled={disabled} />
|
||||
<Switch.Label
|
||||
className={clsx(
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function ThemeIcon(props: {className?: string}) {
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<button onClick={toggleTheme} className={'w-fit'}>
|
||||
<button onClick={toggleTheme} className={'w-fit'} data-testid="settings-dark-light-toggle">
|
||||
<Row className="items-center gap-1 border-2 border-gray-500 rounded-full p-1 w-fit mx-2 px-3 hover:bg-canvas-100">
|
||||
{icon}
|
||||
{children}
|
||||
|
||||
@@ -10,8 +10,9 @@ export default function ShortToggle(props: {
|
||||
className?: string
|
||||
colorMode?: ToggleColorMode
|
||||
size?: 'sm'
|
||||
testId?: string
|
||||
}) {
|
||||
const {on, size, setOn, disabled, className, colorMode = 'primary'} = props
|
||||
const {on, size, setOn, disabled, className, colorMode = 'primary', testId} = props
|
||||
|
||||
const toggleBaseClasses =
|
||||
'group relative inline-flex flex-shrink-0 rounded-full border-2 border-transparent ring-offset-2 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2'
|
||||
@@ -34,6 +35,7 @@ export default function ShortToggle(props: {
|
||||
|
||||
return (
|
||||
<Switch
|
||||
data-testid={testId}
|
||||
disabled={disabled}
|
||||
checked={on}
|
||||
onChange={setOn}
|
||||
|
||||
Reference in New Issue
Block a user