Skip to content

Commit 484e5b5

Browse files
committed
Refactor option handling in tests: implement scoped locators for clearer section differentiation, resolve misaligned inputs, and centralize filter reset logic in clearFilters for consistent pre-test setup.
1 parent 73b89d5 commit 484e5b5

5 files changed

Lines changed: 92 additions & 32 deletions

File tree

tests/e2e/web/pages/peoplePage.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,24 @@ export class PeoplePage {
310310
await this.resetFilters.click()
311311
}
312312

313+
/**
314+
* Start from the whole directory rather than from whatever the account happens to be looking for.
315+
*
316+
* A first visit seeds the search from the member's own "Who I'm looking for" preferences (see
317+
* `getLookingForFilters` in web/components/filters/use-filters.ts), and a seeded test account gets a
318+
* single random preferred gender, one connection type and a narrow age band — so the People page
319+
* frequently opens on "No profiles found", and anything that reads the profile count or picks a card
320+
* fails for reasons that have nothing to do with what the test is checking.
321+
*
322+
* The reset button only renders when something is actually selected, hence the count check.
323+
*/
324+
async clearFilters() {
325+
await this.openFilters()
326+
if ((await this.resetFilters.count()) > 0) await this.resetFilters.click()
327+
await this.closeFilters()
328+
await expect(this.profileGrid).toBeVisible()
329+
}
330+
313331
async setLookingForFilters() {
314332
await this.openFilters()
315333
await expect(this.lookingForCheckbox).toBeVisible()
@@ -517,7 +535,7 @@ export class PeoplePage {
517535
if (!totalProfiles || !filterdProfiles) return
518536
await expect(parseInt(totalProfiles)).not.toEqual(parseInt(filterdProfiles))
519537
} else {
520-
const noProfilesFound = await this.page.getByText('No profiles found.', {exact: true})
538+
const noProfilesFound = await this.page.getByText('No profiles found', {exact: true})
521539
await expect(noProfilesFound).toBeVisible()
522540
}
523541
}

tests/e2e/web/pages/signUpPage.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ export class SignUpPage {
7979
private readonly jobTitleField: Locator
8080
private readonly companyField: Locator
8181
private readonly universityCheckbox: Locator
82+
private readonly interestsSection: Locator
83+
private readonly causesSection: Locator
84+
private readonly workAreaSection: Locator
8285
private readonly addWorkAreaField: Locator
8386
private readonly addWorkAreaButton: Locator
8487
private readonly politicalBeliefDetailsField: Locator
@@ -130,16 +133,23 @@ export class SignUpPage {
130133
this.neutralOnWantingKids = page.getByRole('radio', {name: 'Neutral'})
131134
this.agreeOnWantingKids = page.getByRole('radio', {name: 'Agree'})
132135
this.stronglyAgreeOnWantingKids = page.getByRole('radio', {name: 'Strongly agree'})
133-
this.addInterestsField = page.getByRole('textbox', {name: 'Search or add'}).first()
134-
this.addInterestsButton = page.getByRole('button', {name: 'Add'}).first()
135-
this.addCausesField = page.getByRole('textbox', {name: 'Search or add'}).nth(1)
136-
this.addCausesButton = page.getByRole('button', {name: 'Add'}).nth(1)
136+
// Scoped to their own section rather than picked off the page by index: Work Area, Causes and
137+
// Interests are all `AddOptionEntry`s with an identically-labelled "Search or add" field, and the
138+
// positional locators had interests and work the wrong way round — so custom interests were being
139+
// typed into Work Area, which is how "Chess" ended up an option in both sections.
140+
this.interestsSection = page.getByTestId('option-entry-interests')
141+
this.causesSection = page.getByTestId('option-entry-causes')
142+
this.workAreaSection = page.getByTestId('option-entry-work')
143+
this.addInterestsField = this.interestsSection.getByRole('textbox', {name: 'Search or add'})
144+
this.addInterestsButton = this.interestsSection.getByRole('button', {name: 'Add'})
145+
this.addCausesField = this.causesSection.getByRole('textbox', {name: 'Search or add'})
146+
this.addCausesButton = this.causesSection.getByRole('button', {name: 'Add'})
137147
this.universityField = page.getByTestId('university')
138148
this.jobTitleField = page.getByTestId('job-title')
139149
this.companyField = page.getByTestId('company')
140150
this.universityCheckbox = page.getByRole('checkbox', {name: 'University'})
141-
this.addWorkAreaField = page.getByRole('textbox', {name: 'Search or add'}).nth(2)
142-
this.addWorkAreaButton = page.getByRole('button', {name: 'Add'}).nth(2)
151+
this.addWorkAreaField = this.workAreaSection.getByRole('textbox', {name: 'Search or add'})
152+
this.addWorkAreaButton = this.workAreaSection.getByRole('button', {name: 'Add'})
143153
this.politicalBeliefDetailsField = page.getByTestId('political-belief-details')
144154
this.religiousBeliefsDetailsField = page.getByTestId('religious-belief-details')
145155
this.opennessPersonalitySlider = page.getByRole('slider').first()
@@ -343,41 +353,41 @@ export class SignUpPage {
343353
if (!interest || interest.length === 0) return
344354

345355
for (let i = 0; i < interest.length; i++) {
346-
const checkbox = this.page.getByRole('checkbox', {name: `${interest[i]}`})
356+
const checkbox = this.interestsSection.getByRole('checkbox', {name: `${interest[i]}`})
347357
const isExisting = (await checkbox.count()) > 0
348358

349359
if (isExisting) {
350-
await expect(this.page.getByRole('checkbox', {name: `${interest[i]}`})).toBeVisible()
351-
await clickCheckbox(this.page.getByRole('checkbox', {name: `${interest[i]}`}))
360+
await expect(checkbox).toBeVisible()
361+
await clickCheckbox(checkbox)
352362
} else {
353363
await expect(this.addInterestsField).toBeVisible()
354364
await expect(this.addInterestsButton).toBeVisible()
355365
await this.addInterestsField.fill(interest[i])
356366
await this.addInterestsButton.click()
357367
}
358-
await expect(this.page.getByRole('checkbox', {name: `${interest[i]}`})).toBeVisible()
359-
await expect(this.page.getByRole('checkbox', {name: `${interest[i]}`})).toBeChecked()
368+
await expect(checkbox).toBeVisible()
369+
await expect(checkbox).toBeChecked()
360370
}
361371
}
362372

363373
async setCauses(cause: (Causes | string)[] | undefined) {
364374
if (!cause || cause?.length === 0) return
365375

366376
for (let i = 0; i < cause.length; i++) {
367-
const checkbox = this.page.getByRole('checkbox', {name: `${cause[i]}`})
377+
const checkbox = this.causesSection.getByRole('checkbox', {name: `${cause[i]}`})
368378
const isExisting = (await checkbox.count()) > 0
369379

370380
if (isExisting) {
371-
await expect(this.page.getByRole('checkbox', {name: `${cause[i]}`})).toBeVisible()
372-
await clickCheckbox(this.page.getByRole('checkbox', {name: `${cause[i]}`}))
381+
await expect(checkbox).toBeVisible()
382+
await clickCheckbox(checkbox)
373383
} else {
374384
await expect(this.addCausesField).toBeVisible()
375385
await expect(this.addCausesButton).toBeVisible()
376386
await this.addCausesField.fill(cause[i])
377387
await this.addCausesButton.click()
378388
}
379-
await expect(this.page.getByRole('checkbox', {name: `${cause[i]}`})).toBeVisible()
380-
await expect(this.page.getByRole('checkbox', {name: `${cause[i]}`})).toBeChecked()
389+
await expect(checkbox).toBeVisible()
390+
await expect(checkbox).toBeChecked()
381391
}
382392
}
383393

@@ -410,7 +420,7 @@ export class SignUpPage {
410420
if (!workArea || workArea?.length === 0) return
411421

412422
for (let i = 0; i < workArea.length; i++) {
413-
const chip = optionChip(this.page, workArea[i])
423+
const chip = optionChip(this.workAreaSection, workArea[i])
414424
const isExisting = (await chip.count()) > 0
415425

416426
if (isExisting) {
@@ -424,8 +434,8 @@ export class SignUpPage {
424434
await this.addWorkAreaButton.click()
425435
await this.page.waitForTimeout(500)
426436
}
427-
await expect(optionChip(this.page, workArea[i])).toBeVisible()
428-
await expect(optionChipInput(this.page, workArea[i])).toBeChecked()
437+
await expect(chip).toBeVisible()
438+
await expect(optionChipInput(this.workAreaSection, workArea[i])).toBeChecked()
429439
}
430440
}
431441

tests/e2e/web/specs/signIn.spec.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {sleep} from 'common/src/util/time'
22

3-
import {TEST_USER_DISPLAY_NAME} from '../../utils/seedDatabase'
43
import {expect, test} from '../fixtures/signInFixture'
54
import {getCardAgeGender, getCardName, getCardSeekingInfo, getCardStar} from '../pages/peoplePage'
65

@@ -17,6 +16,7 @@ test.describe('when given valid input', () => {
1716
test('should be able to save/favorite people', async ({app, signedOutAccount: account}) => {
1817
await app.signinWithEmail(account)
1918
await app.home.clickPeopleLink()
19+
await app.people.clearFilters()
2020
const profile = await app.people.getProfileInfo()
2121
const star = await getCardStar(profile)
2222
const name = await getCardName(profile)
@@ -30,6 +30,7 @@ test.describe('when given valid input', () => {
3030
test.describe('the applied filter should', () => {
3131
test('update the profile count', async ({app, signedInAccount}) => {
3232
await app.home.clickPeopleLink()
33+
await app.people.clearFilters()
3334

3435
const totalProfiles = await app.people.profileCountLocator.textContent()
3536
await app.people.setConnectionTypeFilter(['Collaboration', 'collaboration'])
@@ -57,6 +58,7 @@ test.describe('when given valid input', () => {
5758
*/
5859
test.skip('show profiles with the correct age', async ({app, signedInAccount}) => {
5960
await app.home.clickPeopleLink()
61+
await app.people.clearFilters()
6062
await app.people.setDisplayFilter({filters: {Age: true}})
6163

6264
const totalProfiles = await app.people.profileCountLocator.textContent()
@@ -77,6 +79,7 @@ test.describe('when given valid input', () => {
7779

7880
test('show profiles with the correct gender', async ({app, signedInAccount}) => {
7981
await app.home.clickPeopleLink()
82+
await app.people.clearFilters()
8083

8184
const totalProfiles = await app.people.profileCountLocator.textContent()
8285
await app.people.setGenderTypeFilter(['Woman', 'female'])
@@ -87,6 +90,7 @@ test.describe('when given valid input', () => {
8790

8891
test('show profiles with the correct education level', async ({app, signedInAccount}) => {
8992
await app.home.clickPeopleLink()
93+
await app.people.clearFilters()
9094

9195
const totalProfiles = await app.people.profileCountLocator.textContent()
9296
await app.people.setBackgroundFilter({education: ['College', 'some-college']})
@@ -97,6 +101,7 @@ test.describe('when given valid input', () => {
97101

98102
test('show profiles with the correct diet', async ({app, signedInAccount}) => {
99103
await app.home.clickPeopleLink()
104+
await app.people.clearFilters()
100105

101106
const totalProfiles = await app.people.profileCountLocator.textContent()
102107
await app.people.setLifestyleFilter({diet: ['Vegetarian', 'veg']})
@@ -107,6 +112,7 @@ test.describe('when given valid input', () => {
107112

108113
test('show profiles with the correct smoking preference', async ({app, signedInAccount}) => {
109114
await app.home.clickPeopleLink()
115+
await app.people.clearFilters()
110116

111117
const totalProfiles = await app.people.profileCountLocator.textContent()
112118
await app.people.setLifestyleFilter({smoker: 'Yes'})
@@ -120,6 +126,7 @@ test.describe('when given valid input', () => {
120126
signedInAccount,
121127
}) => {
122128
await app.home.clickPeopleLink()
129+
await app.people.clearFilters()
123130

124131
const totalProfiles = await app.people.profileCountLocator.textContent()
125132
await app.people.setLifestyleFilter({psychedelics: ['Regularly (weekly+)', 'regularly']})
@@ -130,6 +137,7 @@ test.describe('when given valid input', () => {
130137

131138
test('show profiles with the correct cannabis preference', async ({app, signedInAccount}) => {
132139
await app.home.clickPeopleLink()
140+
await app.people.clearFilters()
133141

134142
const totalProfiles = await app.people.profileCountLocator.textContent()
135143
await app.people.setLifestyleFilter({
@@ -142,6 +150,7 @@ test.describe('when given valid input', () => {
142150

143151
test('show profiles with the correct political preference', async ({app, signedInAccount}) => {
144152
await app.home.clickPeopleLink()
153+
await app.people.clearFilters()
145154

146155
const totalProfiles = await app.people.profileCountLocator.textContent()
147156
await app.people.setValuesAndBeliefsFilter({political: ['Progressive', 'progressive']})
@@ -152,6 +161,7 @@ test.describe('when given valid input', () => {
152161

153162
test('show profiles with the correct religion preference', async ({app, signedInAccount}) => {
154163
await app.home.clickPeopleLink()
164+
await app.people.clearFilters()
155165

156166
const totalProfiles = await app.people.profileCountLocator.textContent()
157167
await app.people.setValuesAndBeliefsFilter({religious: ['Jewish', 'jewish']})
@@ -161,10 +171,14 @@ test.describe('when given valid input', () => {
161171
})
162172
})
163173

174+
// Each test hides `signedOutAccount` — a profile the fixture seeds and deletes for this test alone —
175+
// rather than a permanent seeded member, so the tests neither depend on `seed-test-data` having been
176+
// run against the database nor leave a hidden-profile row behind on a shared account.
164177
test.describe('the hide profile feature', () => {
165-
test('should correctly hide a profile', async ({app, signedInAccount}) => {
178+
test('should correctly hide a profile', async ({app, signedInAccount, signedOutAccount}) => {
166179
await app.home.clickPeopleLink()
167-
await app.people.useSearch(TEST_USER_DISPLAY_NAME)
180+
await app.people.clearFilters()
181+
await app.people.useSearch(signedOutAccount.display_name)
168182
await sleep(1000)
169183
const profile = await app.people.getProfileInfo()
170184
if (!profile) throw new Error('Profile not found')
@@ -180,9 +194,10 @@ test.describe('when given valid input', () => {
180194
).toBeVisible()
181195
})
182196

183-
test('should be reversible using undo', async ({app, signedInAccount}) => {
197+
test('should be reversible using undo', async ({app, signedInAccount, signedOutAccount}) => {
184198
await app.home.clickPeopleLink()
185-
await app.people.useSearch(TEST_USER_DISPLAY_NAME)
199+
await app.people.clearFilters()
200+
await app.people.useSearch(signedOutAccount.display_name)
186201
await sleep(1000)
187202
const profile = await app.people.getProfileInfo()
188203
if (!profile) throw new Error('Profile not found')
@@ -205,9 +220,11 @@ test.describe('when given valid input', () => {
205220
test('should be reversible using manage hidden profiles feature in settings', async ({
206221
app,
207222
signedInAccount,
223+
signedOutAccount,
208224
}) => {
209225
await app.home.clickPeopleLink()
210-
await app.people.useSearch(TEST_USER_DISPLAY_NAME)
226+
await app.people.clearFilters()
227+
await app.people.useSearch(signedOutAccount.display_name)
211228
await sleep(1000)
212229
const profile = await app.people.getProfileInfo()
213230
if (!profile) throw new Error('Profile not found')
@@ -228,6 +245,7 @@ test.describe('when given valid input', () => {
228245
await app.settings.unhideProfiles(name)
229246
await app.settings.clickCloseButton()
230247
await app.home.clickPeopleLink()
248+
await app.people.clearFilters()
231249
const undoProfile = app.people.page.getByRole('heading', {name: `${name}`})
232250
await expect(undoProfile).toBeVisible()
233251
})
@@ -264,6 +282,7 @@ test.describe('when given valid input', () => {
264282
const longMessage = message.repeat(20)
265283

266284
await app.home.clickPeopleLink()
285+
await app.people.clearFilters()
267286
await app.people.useSearch(receiver.display_name)
268287
await app.people.messageProfile(receiver.display_name, longMessage)
269288
await app.messages.verifyMessage(longMessage)

tests/e2e/web/utils/optionChip.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,22 @@ export async function clickCheckbox(checkbox: Locator) {
2020
await target.click()
2121
}
2222

23-
/** The clickable chip for `label` — its enclosing <label> element. See {@link clickCheckbox}. */
24-
export function optionChip(page: Page, label: string): Locator {
25-
return page.locator('label').filter({has: page.getByRole('checkbox', {name: label, exact: true})})
23+
/**
24+
* The clickable chip for `label` — its enclosing <label> element. See {@link clickCheckbox}.
25+
*
26+
* `root` is usually the page, but pass a section locator where the same option label appears in more
27+
* than one group on screen (Work Area / Causes / Interests all offer the same free-form options), or
28+
* the locator resolves to several chips and Playwright fails on strict mode.
29+
*/
30+
export function optionChip(root: Page | Locator, label: string): Locator {
31+
// The `has:` locator is re-rooted at each candidate <label>, selector chain and all — so it has to be
32+
// built from the page, not from `root`. Scoped from `root` it would look for a section *inside* the
33+
// label and match nothing.
34+
const page = 'goto' in root ? root : root.page()
35+
return root.locator('label').filter({has: optionChipInput(page, label)})
2636
}
2737

2838
/** The chip's underlying checkbox — for `toBeChecked()` assertions, which need the input itself. */
29-
export function optionChipInput(page: Page, label: string): Locator {
30-
return page.getByRole('checkbox', {name: label, exact: true})
39+
export function optionChipInput(root: Page | Locator, label: string): Locator {
40+
return root.getByRole('checkbox', {name: label, exact: true})
3141
}

web/components/add-option-entry.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export function AddOptionEntry(props: {
2222
Object.entries(invert(choices)).sort((a, b) => a[0].localeCompare(b[0], locale)),
2323
)
2424
return (
25-
<Col className={clsx(colClassName)}>
25+
// Several of these sections sit in the same form and share option labels (a member can add
26+
// "Chess" as both a work area and an interest), so the test id is what lets a locator say
27+
// *which* section's "Search or add" field or option chip it means.
28+
<Col className={clsx(colClassName)} data-testid={`option-entry-${label}`}>
2629
{title && <label className={clsx(labelClassName)}>{title}</label>}
2730
<MultiCheckbox
2831
choices={sortedChoices}

0 commit comments

Comments
 (0)