Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
58dfabf
Added Database checks to the onboarding flow
O-Bots Feb 22, 2026
11f8477
Added compatibility page setup
O-Bots Feb 25, 2026
dd01e0b
Finished up the onboarding flow suite
O-Bots Mar 4, 2026
d6e31ba
.
O-Bots Mar 4, 2026
eb7825c
Fix: Merge conflict
O-Bots Mar 11, 2026
56b4a9b
.
O-Bots Mar 13, 2026
4e6d4f3
Fix: Added fix for None discriptive error issue #36
O-Bots Mar 15, 2026
17072c9
Linting and Prettier
O-Bots Mar 15, 2026
5129e8e
Minor cleaning
MartinBraquet Mar 15, 2026
224f0c8
Added Google account to the Onboarding flow
O-Bots Mar 17, 2026
0016c69
Added account cleanup for google accounts
O-Bots Mar 18, 2026
7bb8eea
Started work on Sign-in tests
O-Bots Mar 25, 2026
89efb0f
Linting and Prettier
O-Bots Mar 25, 2026
ef63832
Added checks to the deleteUser func to check if the accout exists
O-Bots Mar 26, 2026
e0561b5
Linting and Prettier
O-Bots Mar 26, 2026
9571d9b
Formatting update, fixed homePage locator for signin
O-Bots Apr 2, 2026
42d28da
.
O-Bots Apr 2, 2026
ca42cfc
.
O-Bots Apr 2, 2026
27ef668
.
O-Bots Apr 2, 2026
b3e3413
Coderabbitai fix's
O-Bots Apr 2, 2026
6e7cd77
Fix
MartinBraquet Apr 3, 2026
7daf458
Improve test utilities and stabilize onboarding flow tests
MartinBraquet Apr 3, 2026
1d898f3
Changes requested
O-Bots Apr 3, 2026
ce0c7cd
Changed POM/Fixture structure to use an app class to instantiate the …
O-Bots Apr 4, 2026
fae4fd7
Apply suggestion from @MartinBraquet
MartinBraquet Apr 4, 2026
6e1b846
Delete .vscode/settings.json
MartinBraquet Apr 4, 2026
90c3f22
Apply suggestion from @MartinBraquet
MartinBraquet Apr 4, 2026
3918830
Apply suggestion from @MartinBraquet
MartinBraquet Apr 4, 2026
d6c2b03
Apply suggestion from @MartinBraquet
MartinBraquet Apr 4, 2026
19e65cb
Linting and Prettier
O-Bots Apr 4, 2026
ede380a
Updated People page
O-Bots May 11, 2026
9bf34a5
Fix app.ts
O-Bots May 11, 2026
c1dacbd
Updated peoplePage.ts: continued adding functions to use filters
O-Bots May 12, 2026
492ee7a
Coderabbitai fix's
O-Bots Apr 2, 2026
8a4aafb
.
O-Bots May 12, 2026
ab72f17
Updated People page
O-Bots May 20, 2026
00a9a85
Lint and Prettier
O-Bots May 20, 2026
b32f468
.
O-Bots May 21, 2026
178dfdc
Continued work on filter tests
O-Bots May 23, 2026
585f9f2
Added more filter tests
O-Bots May 24, 2026
4c2bd90
Added tests for hiding profiles
O-Bots May 25, 2026
00e7662
Added a context manager to test with multiple accounts interacting wi…
O-Bots May 27, 2026
23eedd0
Added Tests for sending/recieving messages
O-Bots May 28, 2026
21a720f
Linting and Prettier
O-Bots May 28, 2026
f490548
Coderabbit suggestions
O-Bots May 28, 2026
8f0910d
CodeRabbit suggestion #2
O-Bots May 28, 2026
eaa0b4c
Fix #1
O-Bots May 28, 2026
75a21b2
Minor fixes
MartinBraquet May 28, 2026
c74c36a
TC fix
MartinBraquet May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/src/choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export const MBTI_CHOICES = {
ESTP: 'estp',
ESFJ: 'esfj',
ESFP: 'esfp',
}
} as const

// MBTI type name mapping
export const MBTI_TYPE_NAMES: Record<string, string> = {
Expand Down
33 changes: 33 additions & 0 deletions tests/e2e/utils/contextManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Browser} from '@playwright/test'

import {App} from '../web/pages/app'

export class ContextManager {
private contexts: Map<string, App> = new Map()

constructor(private browser: Browser) {}

async createContext(customName?: string): Promise<App> {
const name = customName ?? crypto.randomUUID().slice(0, 6)
const existing = this.contexts.get(name)
// Return the existing one instead of closing it?
if (existing) await existing.page.context().close()

const context = await this.browser.newContext()
const page = await context.newPage()
const app = new App(page)
this.contexts.set(name, app)
return app
}

getContext(name: string): App | undefined {
return this.contexts.get(name)
}

async closeAll(): Promise<void> {
for (const app of this.contexts.values()) {
await app.page.context().close()
}
this.contexts.clear()
}
}
30 changes: 30 additions & 0 deletions tests/e2e/utils/firebaseUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ export async function findUser(idToken: string) {
}
}

export async function sendVerificationEmail(idToken: string) {
await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SEND_EMAIL_VERIFICATION}`, {
requestType: 'VERIFY_EMAIL',
idToken,
})
}
export async function getOobCode(oobCodes: any[], email: string) {
return oobCodes.find((item) => item.email.toLowerCase() === email.toLowerCase())?.oobCode
}

export async function verifyEmail(email: string, password: string) {
try {
const loginInfo = await firebaseLoginEmailPassword(email, password)
await sendVerificationEmail(loginInfo.data.idToken)
const oobResponse = await axios.get(`${config.FIREBASE_URL.FIREBASE_EMULATOR_API}`)
const oobCode = await getOobCode(oobResponse.data.oobCodes, email)
if (!oobCode) throw new Error(`No verification OOB code found for email: ${email}`)

const response = await axios.post(
`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.CONFIRM_EMAIL_VERIFICATION}`,
{
oobCode,
},
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (err: any) {
console.log(err)
throw err
}
}

export async function firebaseSignUp(email: string, password: string) {
try {
const response = await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SIGNUP}`, {
Expand Down
17 changes: 16 additions & 1 deletion tests/e2e/utils/seed-test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import {createSomeNotifications} from 'shared/create-notification'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'

import {seedUser} from './seedDatabase'
import {
seedUser,
TEST_USER_DISPLAY_NAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD,
TEST_USER_USERNAME,
} from './seedDatabase'

async function seedCompatibilityPrompts(userId: string | null = null) {
// Need some prompts to prevent the onboarding from stopping once it reaches them (just after profile creation)
Expand Down Expand Up @@ -59,6 +65,15 @@ type ProfileType = 'basic' | 'medium' | 'full'
}
}

// Used in some tests that require interaction with a permanent user
await seedUser(
TEST_USER_EMAIL,
TEST_USER_PASSWORD,
'full',
TEST_USER_DISPLAY_NAME,
TEST_USER_USERNAME,
)

await seedCompatibilityPrompts()
await seedNotifications()

Expand Down
9 changes: 8 additions & 1 deletion tests/e2e/utils/seedDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {insert} from 'shared/supabase/utils'
import {getUser} from 'shared/utils'

import UserAccountInformationForSeeding from '../backend/utils/userInformation'
import {firebaseSignUp} from './firebaseUtils'
import {firebaseSignUp, verifyEmail} from './firebaseUtils'

export const TEST_USER_EMAIL = 'user@compass.test'
export const TEST_USER_PASSWORD = 'pass'
export const TEST_USER_DISPLAY_NAME = 'Test User'
export const TEST_USER_USERNAME = 'TestUser'

/**
* Function used to populate the database with profiles.
Expand Down Expand Up @@ -151,6 +156,7 @@ export async function seedUser(
profileType?: string | undefined,
displayName?: string | undefined,
userName?: string | undefined,
verifyUserEmail?: boolean,
) {
const userInfo = new UserAccountInformationForSeeding()
if (email) userInfo.email = email
Expand All @@ -162,4 +168,5 @@ export async function seedUser(
// Fall back to the pre-generated faker id when Firebase is unreachable
const created = await seedDbUser(userInfo, profileType ?? 'full')
if (created) debug('User created in Supabase:', userInfo.email)
if (verifyUserEmail) await verifyEmail(userInfo.email, userInfo.password)
}
3 changes: 3 additions & 0 deletions tests/e2e/web/SPEC_CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ export const config = {
BASE_URL: 'http://localhost:3000',
FIREBASE_URL: {
BASE: 'http://localhost:9099/identitytoolkit.googleapis.com/v1',
FIREBASE_EMULATOR_API: 'http://localhost:9099/emulator/v1/projects/compass-57c3c/oobCodes',
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',
SEND_EMAIL_VERIFICATION: '/accounts:sendOobCode?key=fake-api-key',
CONFIRM_EMAIL_VERIFICATION: '/accounts:update?key=fake-api-key',
},
USERS: {
DEV_1: {
Expand Down
46 changes: 45 additions & 1 deletion tests/e2e/web/fixtures/signInFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {deleteUser} from '../utils/deleteUser'

export const test = base.extend<{
app: App
dev_one_account: UserAccountInformation
devOneAccount: UserAccountInformation
devTwoAccount: UserAccountInformation
specAccount: UserAccountInformation
fakerAccount: UserAccountInformation
googleAccountOne: UserAccountInformation
googleAccountTwo: UserAccountInformation
Expand All @@ -17,6 +19,7 @@ export const test = base.extend<{
app: async ({page}, use) => {
const appPage = new App(page)
await use(appPage)
await appPage.contextManager?.closeAll()
},
signedInAccount: async ({app}: {app: App}, use) => {
const account = testAccounts.faker_account()
Expand All @@ -26,6 +29,7 @@ export const test = base.extend<{
undefined,
account.display_name,
account.username,
true,
)
await app.signinWithEmail(account)
await use(account)
Expand All @@ -39,6 +43,46 @@ export const test = base.extend<{
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
devOneAccount: async ({}, use) => {
const account = testAccounts.dev_one_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
devTwoAccount: async ({}, use) => {
const account = testAccounts.dev_two_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
specAccount: async ({}, use) => {
const account = testAccounts.spec_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
Expand Down
17 changes: 14 additions & 3 deletions tests/e2e/web/pages/app.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {Page} from '@playwright/test'
import {BrowserContext, Page} from '@playwright/test'

import {ContextManager} from '../../utils/contextManager'
import {UserAccountInformation} from '../utils/accountInformation'
import {AuthPage} from './authPage'
import {CompatibilityPage} from './compatibilityPage'
import {HomePage} from './homePage'
import {MessagesPage} from './messagesPage'
import {NotificationPage} from './notificationsPage'
import {OnboardingPage} from './onboardingPage'
import {OrganizationPage} from './organizationPage'
import {PeoplePage} from './peoplePage'
import {ProfilePage} from './profilePage'
import {SettingsPage} from './settingsPage'
import {SignUpPage} from './signUpPage'
import {SocialPage} from './socialPage'
import {PeoplePage} from './peoplePage'
import {NotificationPage} from './notificationsPage'

export class App {
readonly auth: AuthPage
Expand All @@ -25,6 +27,9 @@ export class App {
readonly social: SocialPage
readonly people: PeoplePage
readonly notifs: NotificationPage
readonly messages: MessagesPage
readonly contextManager: ContextManager
readonly context: BrowserContext

constructor(public readonly page: Page) {
this.auth = new AuthPage(page)
Expand All @@ -38,6 +43,12 @@ export class App {
this.social = new SocialPage(page)
this.people = new PeoplePage(page)
this.notifs = new NotificationPage(page)
this.messages = new MessagesPage(page)
this.context = page.context()

const browser = page.context().browser()
if (!browser) throw new Error('Could not get Browser from page.context().browser()')
this.contextManager = new ContextManager(browser)
}

async deleteProfileFromSettings() {
Expand Down
108 changes: 108 additions & 0 deletions tests/e2e/web/pages/messagesPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {expect, Locator, Page} from '@playwright/test'
import {sleep} from 'common/util/time'

export class MessagesPage {
private readonly messagesPageHeader: Locator
private readonly messagesTable: Locator
private readonly messagesRow: Locator
private readonly messagesUsername: Locator
private readonly messagesTimestamp: Locator
private readonly newMessageButton: Locator
private readonly newMessageSearchUsers: Locator
private readonly newMessageSearchResults: Locator
private readonly newMessageSearchCreateButton: Locator
private readonly newMessageStart: Locator
private readonly messageInput: Locator
private readonly messageSubmit: Locator
private readonly conversation: Locator
private readonly conversationMessage: Locator

constructor(public readonly page: Page) {
this.messagesPageHeader = page.getByRole('heading', {name: 'Messages'})
this.messagesTable = page.getByTestId('messages-table')
this.messagesRow = page.getByTestId('messages-row')
this.messagesUsername = page.getByTestId('messages-username')
this.messagesTimestamp = page.getByTestId('messages-timestamp')
this.newMessageButton = page.getByRole('button', {name: 'New Message'})
this.newMessageSearchUsers = page.getByRole('textbox', {name: 'Search users...'})
this.newMessageSearchResults = page.getByTestId('search-results')
this.newMessageSearchCreateButton = page.getByRole('button', {name: 'Create'})
this.newMessageStart = page.getByText('No messages yet.', {exact: true})
this.messageInput = page.locator('.tiptap')
this.messageSubmit = page.getByTestId('conversation-message-submit')
this.conversation = page.getByTestId('conversation')
this.conversationMessage = page.getByTestId('conversation-message')
}

async verifyMessagesPage() {
await expect(this.messagesPageHeader).toBeVisible()
}

async createNewMessage(username: string[]) {
await expect(this.newMessageButton).toBeVisible()
await this.newMessageButton.click()
await expect(this.newMessageSearchUsers).toBeVisible()
for (let i = 0; i < username.length; i++) {
await this.newMessageSearchUsers.fill(username[i])
await sleep(1000)
await expect(this.newMessageSearchResults).toBeVisible()
const results = await this.newMessageSearchResults
.getByTestId('search-results-username')
.all()
const targetUser = username[i].toLowerCase()
for (let j = 0; j < results.length; j++) {
const usernameResults = (await results[j].textContent())?.toLowerCase()
if (usernameResults === targetUser) {
await results[j].click()
break
}
}
}

await expect(this.newMessageSearchCreateButton).toBeVisible()
await this.newMessageSearchCreateButton.click()
}

async sendMessage(message: string) {
await expect(this.messageInput).toBeVisible()
await this.messageInput.fill(message)
await expect(this.messageSubmit).toBeVisible()
await this.messageSubmit.click()
const verified = await this.verifyMessage(message)
if (!verified)
throw new Error(`Message "${message}" was not found in conversation after sending`)
}

async findMessageConversation(displayName: string) {
await expect(this.messagesTable).toBeVisible()
await this.page.waitForTimeout(1000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove waitForTimeout – use Playwright's built-in auto-waiting instead.

This violates the explicit guideline: "Never use page.waitForTimeout(). Use Playwright's built-in auto-waiting or waitForURL / waitForSelector."

Fixed timeouts introduce flakiness and are discouraged as "sleep hacks for eventual consistency." Playwright's count() and visibility checks already include auto-waiting, making this unnecessary.

🔧 Proposed fix to remove the timeout
  async findMessageConversation(displayName: string) {
    await expect(this.messagesTable).toBeVisible()
-   await this.page.waitForTimeout(1000)
    const doMessagesExist = (await this.messagesRow.count()) > 0

If additional synchronization is needed, use:

await expect(this.messagesRow.first()).toBeVisible({timeout: 5000})

As per coding guidelines: Never use page.waitForTimeout(). Use Playwright's built-in auto-waiting or waitForURL / waitForSelector.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await this.page.waitForTimeout(1000)
async findMessageConversation(displayName: string) {
await expect(this.messagesTable).toBeVisible()
const doMessagesExist = (await this.messagesRow.count()) > 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/web/pages/messagesPage.ts` at line 74, Remove the hard-coded sleep
call this.page.waitForTimeout(1000) in messagesPage and replace it with
Playwright auto-waiting: wait for the UI element represented by messagesRow
(e.g., this.messagesRow.first()) to be visible or present using Playwright's
expect/toBeVisible or a waitForSelector/waitForURL call with a timeout (e.g.,
5s) so the test synchronizes reliably without fixed sleeps.

const doMessagesExist = (await this.messagesRow.count()) > 0
if (doMessagesExist) {
const messages = await this.messagesRow.getByTestId('messages-username').all()

for (let i = 0; i < messages.length; i++) {
await expect(messages[i]).toBeVisible()
const messageFromUser = await messages[i].textContent()
if (messageFromUser?.toLowerCase() === displayName.toLowerCase()) await messages[i].click()
}
} else {
throw new Error('There are no messages on this account')
}
}

async verifyMessage(messageContent: string) {
await expect(this.conversation).toBeVisible()
await sleep(1000)
const messageCount = (await this.conversationMessage.count()) > 0
if (messageCount) {
const messages = await this.conversationMessage.all()
for (let i = 0; i < messages.length; i++) {
const message = await messages[i].textContent()
if (message?.toLowerCase() === messageContent.toLowerCase()) return true
}
return false
} else {
throw new Error('There are no messages in this conversation')
}
}
}
Loading
Loading