Added Tests for sending/recieving messages

Added tests for staring/favoriting profiles
Added testIds where necessary
Added messagesPage.ts
Updated peoplePage.ts
This commit is contained in:
Okechi Jones-Williams
2026-05-28 19:15:48 +01:00
parent 00e7662d60
commit 23eedd0fbb
13 changed files with 226 additions and 12 deletions

View File

@@ -13,6 +13,8 @@ import {SignUpPage} from './signUpPage'
import {SocialPage} from './socialPage'
import {PeoplePage} from './peoplePage'
import {NotificationPage} from './notificationsPage'
import { MessagesPage } from './messagesPage'
export class App {
readonly auth: AuthPage
@@ -26,6 +28,7 @@ export class App {
readonly social: SocialPage
readonly people: PeoplePage
readonly notifs: NotificationPage
readonly messages: MessagesPage
readonly contextManager: ContextManager
readonly context: BrowserContext
@@ -41,6 +44,7 @@ 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()

View File

@@ -0,0 +1,97 @@
import {expect, Locator, Page} from '@playwright/test'
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 expect(this.newMessageSearchResults).toBeVisible()
const results = await this.newMessageSearchResults.getByTestId('search-results-username').all()
for (let i = 0; i < results.length; i++) {
const usernameResults = await results[i].textContent()
if (usernameResults?.toLowerCase() === username[i].toLowerCase()) await results[i].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()
}
async findMessageConversation(displayName: string) {
await expect(this.messagesTable).toBeVisible()
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()
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')
}
}
}

View File

@@ -88,7 +88,10 @@ export type PeoplePageFilter = {
export class PeoplePage {
private readonly peopleHeading: Locator
private readonly savedPeopleHeading: Locator
private readonly savedPeopleList: Locator
private readonly searchBox: Locator
private readonly savedPeopleButton: Locator
private readonly profileCount: Locator
private readonly resetFilters: Locator
private readonly yourFiltersCheckbox: Locator
@@ -122,13 +125,20 @@ export class PeoplePage {
private readonly displayDropdown: Locator
private readonly profileGrid: Locator
private readonly profileResults: Locator
private readonly profileHide: Locator
private readonly profileStar: Locator
private readonly profileMessage: Locator
private readonly messageInput: Locator
private readonly profileName: Locator
private readonly profileAgeGender: Locator
private readonly profileSeeking: Locator
constructor(public readonly page: Page) {
this.peopleHeading = page.getByRole('heading', {name: 'People'})
this.savedPeopleHeading = page.getByRole('heading', { name: 'Saved People' })
this.savedPeopleList = page.getByTestId('saved-person')
this.searchBox = page.getByRole('textbox', {name: 'Search anything...'})
this.savedPeopleButton = page.getByRole('button', { name: 'Saved People' })
this.profileCount = page.getByTestId('people-profile-count')
this.resetFilters = page.getByRole('button', {name: 'Reset filters'})
this.yourFiltersCheckbox = page.getByText('Your filters', {exact: true})
@@ -162,6 +172,10 @@ export class PeoplePage {
this.displayDropdown = page.getByRole('button', {name: 'Display'})
this.profileGrid = page.getByTestId('people-profile-grid')
this.profileResults = page.getByTestId('people-profile-results')
this.profileHide = page.getByTestId('hide-profile-button')
this.profileStar = page.getByTestId('star-profile-button')
this.profileMessage = page.getByTestId('message-profile-button')
this.messageInput = page.locator('.tiptap')
this.profileName = page.getByTestId('people-profile-name')
this.profileAgeGender = page.getByTestId('people-profile-age-gender')
this.profileSeeking = page.getByTestId('people-profile-seeking')
@@ -247,12 +261,16 @@ export class PeoplePage {
await expect(this.peopleHeading).toBeVisible()
}
//Doesn't actually work, need to find out why
async clickSavedPeopleButton() {
await expect(this.savedPeopleButton).toBeVisible()
await this.savedPeopleButton.click()
}
async useSearch(item: string) {
await expect(this.searchBox).toBeVisible()
await this.searchBox.click()
await this.searchBox.fill(item)
await this.page.keyboard.press('Enter')
await this.page.waitForTimeout(1000)
}
async resetFilter() {
@@ -443,12 +461,18 @@ export class PeoplePage {
const profileName = await chosenProfile.getByTestId('people-profile-name').textContent()
const ageGender = await chosenProfile.getByTestId('people-profile-age-gender').textContent()
const seekingInfo = await chosenProfile.getByTestId('people-profile-seeking').textContent()
const hideProfile = await chosenProfile.getByTestId('hide-profile-button')
const starProfile = await chosenProfile.getByTestId('star-profile-button')
const messageProfile = await chosenProfile.getByTestId('message-profile-button')
return {
profile: chosenProfile ?? '',
name: profileName ?? '',
ageGender: ageGender ?? '',
seeking: seekingInfo ?? '',
hide: hideProfile ?? '',
star: starProfile ?? '',
message: messageProfile ?? '',
}
}
@@ -465,4 +489,41 @@ export class PeoplePage {
await expect(noProfilesFound).toBeVisible()
}
}
async selectProfile(displayName: string) {
await expect(this.profileGrid).toBeVisible()
await this.profileName.getByText(displayName).click()
}
async messageProfile(displayName: string, message: string) {
await expect(this.profileGrid).toBeVisible()
const profiles = await this.profileResults.all()
for (let i = 0; i < profiles.length; i++) {
const profileName = await profiles[i].getByTestId('people-profile-name').textContent();
if (profileName?.toLowerCase() === displayName.toLowerCase()) {
await profiles[i].getByTestId('message-profile-button').click()
await expect(this.messageInput).toBeVisible()
await this.messageInput.fill(message)
await this.page.getByTestId('conversation-message-submit').click()
}
}
}
async verifySavedPerson(displayName: string) {
await expect(this.savedPeopleHeading).toBeVisible()
const isThereSavedPeople = await this.savedPeopleList.count() > 0
if (isThereSavedPeople) {
const listOfPeople = await this.savedPeopleList.all()
for (let i = 0; i < listOfPeople.length; i++) {
await expect(listOfPeople[i]).toBeVisible()
const profileName = await listOfPeople[i].textContent()
if (profileName?.toLowerCase() === displayName.toLowerCase()) return true
}
return false
} else {
throw new Error('There are no profiles in the saved people list')
}
}
}

View File

@@ -1,5 +1,4 @@
import {expect, test} from '../fixtures/signInFixture'
import {ContextManager} from '../../utils/contextManager'
test.describe('when given valid input', () => {
test('should be able to sign in to an available account', async ({
@@ -11,6 +10,19 @@ test.describe('when given valid input', () => {
await app.home.verifySignedInHomePage(account.display_name)
})
test('should be able to save/favorite people', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const profile = await app.people.getProfileInfo()
await expect(profile.star).toBeVisible()
await profile.star.click()
await app.people.clickSavedPeopleButton()
await app.people.verifySavedPerson(profile.name)
})
test.describe('the applied filter should', () => {
test('update the profile count', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
@@ -226,14 +238,39 @@ test.describe('when given valid input', () => {
})
test.describe('a verified account should', () => {
test('be able to send a message', async ({app, devOneAccount, devTwoAccount}) => {
test('be able to send a message from the messages page', async ({app, devOneAccount, devTwoAccount}) => {
const devOne = await app.contextManager.createContext('devOne')
const devTwo = await app.contextManager.createContext('devTwo')
await devOne.signinWithEmail(devOneAccount)
await devOne.home.clickPeopleLink()
await devTwo.signinWithEmail(devTwoAccount)
await devTwo.home.clickPeopleLink()
await devOne.home.clickMessagesLink()
await devOne.messages.createNewMessage([devTwoAccount.display_name])
await devOne.messages.sendMessage('This is a message')
await devTwo.home.clickMessagesLink()
await devTwo.messages.findMessageConversation(devOneAccount.display_name)
await devTwo.messages.verifyMessage('This is a message')
})
test('be able to send a message from the people page', async ({app, devOneAccount, devTwoAccount}) => {
const devOne = await app.contextManager.createContext('devOne')
const devTwo = await app.contextManager.createContext('devTwo')
await devOne.signinWithEmail(devOneAccount)
await devTwo.signinWithEmail(devTwoAccount)
await devOne.home.clickPeopleLink()
await devOne.people.useSearch(devTwoAccount.display_name)
const message = "This is a message".repeat(20)
await devOne.people.messageProfile(devTwoAccount.display_name, message)
await devOne.messages.verifyMessage(message)
await devTwo.home.clickMessagesLink()
await devTwo.messages.findMessageConversation(devOneAccount.display_name)
await devTwo.messages.verifyMessage(message)
})
})
})

View File

@@ -95,6 +95,7 @@ export function ChatMessageItem(props: {
isMe && 'flex-row-reverse',
firstOfUser ? 'mt-2' : 'mt-1',
)}
data-testid="conversation-message"
>
{!isMe && !hideAvatar && (
<MessageAvatar

View File

@@ -227,6 +227,7 @@ export function CommentInputTextArea(props: {
)}
{!isSubmitting && !isDisabled && submit && commentTypes.includes('comment') && (
<button
data-testid="conversation-message-submit"
className="text-ink-500 hover:text-ink-700 active:bg-ink-300 disabled:text-ink-300 px-4 transition-colors"
disabled={isDisabled || !editor || editor.isEmpty}
onClick={() => submit('comment')}

View File

@@ -431,7 +431,10 @@ function ProfilePreview(props: {
/>
)}
{user && (
<div className={clsx(cardSize !== 'large' && 'hidden sm:flex')}>
<div
className={clsx(cardSize !== 'large' && 'hidden sm:flex')}
data-testid="message-profile-button"
>
<SendMessageButton
toUser={user}
profile={profile}

View File

@@ -230,7 +230,7 @@ function StarModal(props: {
</p>
<Col className={clsx('divide-y divide-canvas-200 w-full pr-2', SCROLLABLE_MODAL_CLASS)}>
{visibleUsers.map((u) => (
<div key={u.id} className="py-3 first:pt-0 last:pb-0">
<div key={u.id} className="py-3 first:pt-0 last:pb-0" data-testid="saved-person">
<div className="bg-canvas-0 border border-canvas-200 rounded-xl p-3 transition-all hover:border-primary-300 hover:shadow-sm">
<Row className="items-center justify-between gap-3">
<Link className="flex-1 group" href={'/' + u.username}>
@@ -243,7 +243,10 @@ function StarModal(props: {
/>
</div>
<Col className="flex-1">
<div className="font-medium text-ink-900 group-hover:text-primary-600 transition-colors">
<div
className="font-medium text-ink-900 group-hover:text-primary-600 transition-colors"
data-testid="saved-person-display-name"
>
{u.name}
</div>
<div className="text-ink-500 text-sm">@{u.username}</div>
@@ -251,6 +254,7 @@ function StarModal(props: {
</Row>
</Link>
<button
data-testid="remove-saved-person"
onClick={() => {
// Optimistically remove the user from the list
setRemovingIds((prev) => new Set(prev).add(u.id))

View File

@@ -94,6 +94,7 @@ export function SelectUsers(props: {
'relative inline-block w-full overflow-y-scroll text-right',
queryReady && 'h-56',
)}
data-testid="search-results"
>
{() => (
<Transition
@@ -112,7 +113,7 @@ export function SelectUsers(props: {
>
<div className="py-1">
{filteredUsers.map((user) => (
<Menu.Item key={user.id}>
<Menu.Item key={user.id} data-testid="search-results-username">
{({active}) => (
<button
className={clsx(

View File

@@ -76,6 +76,7 @@ export function HideProfileButton(props: HideProfileButtonProps) {
noTap
>
<button
data-testid="hide-profile-button"
className={clsx(
'relative inline-flex items-center justify-center border bg-canvas-50 border-canvas-300 text-ink-300 hover:border-primary-400 hover:bg-primary-50 rounded-lg h-7 w-7',
className,

View File

@@ -43,6 +43,7 @@ export const StarButton = (props: {
const button = (
<button
data-testid="star-profile-button"
className={clsx(
'border border-canvas-200',
buttonClass('xs', 'none'),

View File

@@ -388,6 +388,7 @@ export const PrivateChat = (props: {
style={{
transform: isSafari ? 'translate3d(0, 0, 0)' : 'none',
}}
data-testid="conversation"
>
<div
className="relative px-1 pb-4 pt-1 transition-all duration-100"

View File

@@ -64,7 +64,7 @@ export function MessagesContent(props: {currentUser: User}) {
</h1>
<NewMessageButton />
</Row>
<Col className={'w-full overflow-hidden gap-2'}>
<Col className={'w-full overflow-hidden gap-2'} data-testid="messages-table">
{channels && channels.length === 0 && (
<div className="bg-canvas-50 border border-canvas-200 rounded-xl p-8 text-center mt-4">
<p className="text-ink-500 text-sm mb-1">
@@ -116,6 +116,7 @@ export const MessageChannelRow = (props: {
className={
'items-center gap-3 bg-canvas-50 border border-canvas-200 rounded-xl p-3 transition-all hover:border-primary-300 hover:shadow-sm hover:bg-canvas-100'
}
data-testid="messages-row"
>
{otherUsers && otherUsers.length > 0 ? (
<MultipleOrSingleAvatars
@@ -135,6 +136,7 @@ export const MessageChannelRow = (props: {
className={
'font-medium text-ink-900 text-sm group-hover:text-primary-600 transition-colors'
}
data-testid="messages-username"
>
{otherUsers && otherUsers.length > 0 ? (
<span>
@@ -162,7 +164,7 @@ export const MessageChannelRow = (props: {
)}
{isBanned && <BannedBadge />}
</span>
<span className={'text-ink-300 text-xs'}>
<span className={'text-ink-300 text-xs'} data-testid="messages-timestamp">
{lastMessage && <RelativeTimestamp time={lastMessage.createdTime} />}
</span>
</Row>