Skip to content

Commit c9fdcbc

Browse files
Playwright tests reliability (#49)
Co-authored-by: me <me@kentcdodds.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent a55259e commit c9fdcbc

5 files changed

Lines changed: 47 additions & 24 deletions

File tree

playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export default defineConfig({
5454
timeout: 120 * 1000,
5555
env: {
5656
PORT,
57+
PLAYWRIGHT_TEST_BASE_URL: `http://localhost:${PORT}`,
5758
},
5859
},
5960
})

tests/db-utils.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { UniqueEnforcer } from 'enforce-unique'
44
import { type PrismaClient } from '#app/utils/prisma-generated.server/client.ts'
55

66
const uniqueUsernameEnforcer = new UniqueEnforcer()
7+
const uniquePhoneEnforcer = new UniqueEnforcer()
8+
9+
export function createPhoneNumber({
10+
countryCode = '+1',
11+
includeCountryCode = true,
12+
}: { countryCode?: string; includeCountryCode?: boolean } = {}) {
13+
return uniquePhoneEnforcer.enforce(() => {
14+
const digits = faker.string.numeric({ length: 10, allowLeadingZeros: true })
15+
return includeCountryCode ? `${countryCode}${digits}` : digits
16+
})
17+
}
718

819
export function createUser() {
920
const firstName = faker.person.firstName()
@@ -26,7 +37,7 @@ export function createUser() {
2637
return {
2738
username,
2839
name: `${firstName} ${lastName}`,
29-
phoneNumber: faker.phone.number(),
40+
phoneNumber: createPhoneNumber(),
3041
}
3142
}
3243

@@ -48,7 +59,7 @@ export function createMessage() {
4859

4960
export function createRecipient() {
5061
return {
51-
phoneNumber: faker.phone.number(),
62+
phoneNumber: createPhoneNumber(),
5263
name: faker.person.fullName(),
5364
verified: faker.datatype.boolean(),
5465
// TODO: make sure this doesn't generate a cron string that's too frequent

tests/e2e/onboarding.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { invariant } from '@epic-web/invariant'
22
import { faker } from '@faker-js/faker'
33
import { prisma } from '#app/utils/db.server.ts'
44
import { deleteText, waitForText } from '#tests/mocks/utils.ts'
5-
import { test as base, createUser, expect } from '#tests/playwright-utils.ts'
5+
import {
6+
test as base,
7+
createPhoneNumber,
8+
createUser,
9+
expect,
10+
} from '#tests/playwright-utils.ts'
611

712
const URL_REGEX = /(?<url>https?:\/\/[^\s$.?#].[^\s]*)/
813
const CODE_REGEX = /code: (?<code>[\d\w]+)/
@@ -16,6 +21,9 @@ function formatPhoneNumber(
1621
countryCode = defaultCountryCode,
1722
) {
1823
const digitsOnly = phoneNumber.replace(/\D/g, '')
24+
if (phoneNumber.trim().startsWith('+')) {
25+
return `+${digitsOnly}`.replace(/\s+/g, '')
26+
}
1927
return `${countryCode}${digitsOnly}`.replace(/\s+/g, '')
2028
}
2129

@@ -29,21 +37,23 @@ const test = base.extend<{
2937
}>({
3038
getOnboardingData: async ({}, use) => {
3139
const userData = createUser()
40+
const phoneNumber = createPhoneNumber({ includeCountryCode: false })
3241
// Clean up any stale text fixtures for this phone number before test
33-
await deleteText(userData.phoneNumber)
34-
await deleteText(formatPhoneNumber(userData.phoneNumber))
42+
await deleteText(phoneNumber)
43+
await deleteText(formatPhoneNumber(phoneNumber))
3544
// eslint-disable-next-line react-hooks/rules-of-hooks
3645
await use(() => {
3746
const onboardingData = {
3847
...userData,
48+
phoneNumber,
3949
password: faker.internet.password(),
4050
}
4151
return onboardingData
4252
})
4353
await prisma.user.deleteMany({ where: { username: userData.username } })
4454
// Clean up text fixtures after test
45-
await deleteText(userData.phoneNumber)
46-
await deleteText(formatPhoneNumber(userData.phoneNumber))
55+
await deleteText(phoneNumber)
56+
await deleteText(formatPhoneNumber(phoneNumber))
4757
},
4858
})
4959

tests/e2e/send.test.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,25 @@ test('Users can write and send a message immediately', async ({
3939

4040
await page.waitForLoadState('domcontentloaded')
4141

42-
const { content: textMessageContent } = createMessage()
42+
const textMessageContent = `Test message ${faker.string.alphanumeric(8)}`
4343
const messageTextbox = page.getByRole('textbox', { name: /message/i })
4444
await messageTextbox.waitFor({ state: 'visible' })
45-
await messageTextbox.fill(textMessageContent)
46-
47-
await page.getByRole('button', { name: /save/i }).click()
48-
await waitFor(
49-
async () => {
50-
const createdMessage = await prisma.message.findFirst({
51-
select: { id: true },
52-
where: { recipientId: recipient.id, content: textMessageContent },
53-
})
54-
if (createdMessage) return createdMessage
55-
},
56-
{ timeout: 15000, errorMessage: 'Message not created' },
57-
)
58-
await page.goto(`/recipients/${recipient.id}`)
45+
await messageTextbox.click()
46+
await messageTextbox.fill('')
47+
await messageTextbox.type(textMessageContent, { delay: 5 })
48+
await expect(messageTextbox).toHaveValue(textMessageContent)
49+
50+
await Promise.all([
51+
page.waitForURL(`/recipients/${recipient.id}`, {
52+
timeout: 30000,
53+
waitUntil: 'domcontentloaded',
54+
}),
55+
page.getByRole('button', { name: /save/i }).click(),
56+
])
5957
await page.waitForLoadState('domcontentloaded')
58+
await expect(page.getByText(textMessageContent)).toBeVisible({
59+
timeout: 20000,
60+
})
6061

6162
const sendNowButton = page.getByRole('button', { name: /send now/i })
6263
await sendNowButton.waitFor({ state: 'visible' })

tests/e2e/settings-profile.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { faker } from '@faker-js/faker'
33
import { verifyUserPassword } from '#app/utils/auth.server.ts'
44
import { prisma } from '#app/utils/db.server.ts'
55
import { deleteText, waitForText } from '#tests/mocks/utils.ts'
6-
import { createUser, expect, test } from '#tests/playwright-utils.ts'
6+
import { createPhoneNumber, createUser, expect, test } from '#tests/playwright-utils.ts'
77

88
const CODE_REGEX = /Here's your verification code: (?<code>[\d\w]+)/
99

@@ -68,7 +68,7 @@ test('Users can update their password', async ({ page, login }) => {
6868

6969
test('Users can change their phone number', async ({ page, login }) => {
7070
const preUpdateUser = await login()
71-
const newPhoneNumber = faker.phone.number()
71+
const newPhoneNumber = createPhoneNumber()
7272
expect(preUpdateUser.phoneNumber).not.toEqual(newPhoneNumber)
7373

7474
// Clean up any stale fixtures for both phone numbers

0 commit comments

Comments
 (0)