Skip to content

Commit 6ef01d6

Browse files
authored
Merge pull request #207 from zainfathoni/feature/rb-testing
feat(e2e): improve testing infrastructure with MSW and flaky test fixes
2 parents 91a4054 + bafd150 commit 6ef01d6

12 files changed

Lines changed: 177 additions & 167 deletions

app/entry.server.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import type { EntryContext } from '@remix-run/node'
22
import { RemixServer } from '@remix-run/react'
33
import { renderToString } from 'react-dom/server'
44

5+
// Start MSW mock server in E2E mode to intercept external API calls
6+
if (process.env.RUNNING_E2E === 'true') {
7+
// Dynamic import to avoid bundling MSW in production
8+
// eslint-disable-next-line @typescript-eslint/no-var-requires
9+
require('../mocks/server').startMockServer()
10+
}
11+
512
export default function handleRequest(
613
request: Request,
714
responseStatusCode: number,

app/routes/dashboard.profile._index.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,12 @@ export default function ProfileIndex() {
6363
</div>
6464
</div>
6565
</div>
66-
<div className="hidden sm:block 2xl:hidden mt-6 min-w-0 flex-1">
67-
<h1
68-
aria-label="Nama Lengkap"
69-
className="text-2xl font-bold text-gray-900 truncate"
70-
>
71-
{user.name}
72-
</h1>
73-
</div>
66+
<h1
67+
aria-hidden="true"
68+
className="hidden sm:block 2xl:hidden mt-6 min-w-0 flex-1 text-2xl font-bold text-gray-900 truncate"
69+
>
70+
{user.name}
71+
</h1>
7472
</div>
7573
</div>
7674

app/services/email.server.tsx

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { User } from '@prisma/client'
22
import { renderToString } from 'react-dom/server'
33
import type { SendEmailFunction } from 'remix-auth-email-link'
44
import * as emailProvider from '~/services/email-provider.server'
5-
import { writeFixture } from '~/utils/fixtures'
65

76
let emailFrom = 'Rumah Berbagi <admin@rumahberbagi.com>'
87
if (process.env.EMAIL_FROM) {
@@ -34,21 +33,17 @@ export const sendEmail: SendEmailFunction<User> = async (options) => {
3433
</main>
3534
)
3635

37-
if (
38-
process.env.RUNNING_E2E === 'true' ||
39-
process.env.NODE_ENV === 'development'
40-
) {
41-
// TODO: Mock the HTTP transport layer properly by using MSW
42-
console.warn(`\n${options.magicLink}\n`)
43-
await writeFixture(`../e2e/fixtures/magic.local.json`, {
44-
magicLink: options.magicLink,
45-
})
46-
} else {
47-
await emailProvider.sendEmail({
48-
to: options.emailAddress,
49-
from: emailFrom,
50-
subject,
51-
html: body,
52-
})
36+
// In E2E mode, MSW intercepts the Mailgun API call and captures the magic link.
37+
// In development, we log the magic link for convenience.
38+
if (process.env.NODE_ENV === 'development') {
39+
console.info(`\n🔗 Magic link: ${options.magicLink}\n`)
5340
}
41+
42+
// Send email via Mailgun API - MSW will intercept in E2E mode
43+
await emailProvider.sendEmail({
44+
to: options.emailAddress,
45+
from: emailFrom,
46+
subject,
47+
html: body,
48+
})
5449
}

e2e/auth-error.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { expect } from '@playwright/test'
2-
import { test } from './base-test'
1+
import { test, expect } from './base-test'
32

43
test.use({
54
storageState: 'e2e/fixtures/auth/nobody.json',

e2e/base-test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import {
2-
fixtures,
3-
TestingLibraryFixtures,
2+
locatorFixtures,
3+
LocatorFixtures,
44
} from '@playwright-testing-library/test/fixture'
5-
import { test as base } from '@playwright/test'
5+
import { test as base, expect } from '@playwright/test'
66

7-
export interface Fixtures extends TestingLibraryFixtures {
7+
export interface Fixtures extends LocatorFixtures {
88
noscript: boolean
99
}
1010

1111
export const test = base.extend<Fixtures>({
12-
...fixtures,
12+
...locatorFixtures,
1313
// Default value for noscript
1414
noscript: [false, { option: true }],
1515
})
16+
17+
// Re-export expect for convenience
18+
export { expect }

e2e/cta.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { expect } from '@playwright/test'
2-
import { test } from './base-test'
1+
import { test, expect } from './base-test'
32

43
test.use({
54
storageState: 'e2e/fixtures/auth/member-no-transaction.local.json',
@@ -8,12 +7,12 @@ test.use({
87
test('Call to action has rendered in dashboard page', async ({ page }) => {
98
await page.goto('/dashboard')
109

11-
await expect(page.locator('text=Biaya kelas').first()).toBeVisible()
10+
await expect(page.getByText('Biaya kelas')).toBeVisible()
1211
await expect(
13-
page.locator('text=Sistem pendaftaran peserta melalui website ini').first()
12+
page.getByText('Sistem pendaftaran peserta melalui website ini')
1413
).toBeVisible()
1514

16-
const purchaseButton = page.locator('a:has-text("Daftarkan diri")').first()
15+
const purchaseButton = page.getByRole('link', { name: /daftarkan diri/i })
1716
await expect(purchaseButton).toBeVisible()
1817
await expect(purchaseButton).toHaveAttribute('href', '/dashboard/purchase')
1918
})
@@ -23,12 +22,11 @@ test('Redirect to /dashboard/purchase after click CTA button', async ({
2322
}) => {
2423
await page.goto('/dashboard')
2524

26-
await expect(page.locator('text=Biaya kelas').first()).toBeVisible()
25+
await expect(page.getByText('Biaya kelas')).toBeVisible()
2726
await expect(
28-
page.locator('text=Sistem pendaftaran peserta melalui website ini').first()
27+
page.getByText('Sistem pendaftaran peserta melalui website ini')
2928
).toBeVisible()
3029

31-
await page.click('text=Daftarkan diri')
32-
33-
await expect(page).toHaveURL('http://localhost:3000/dashboard/purchase')
30+
await page.getByRole('link', { name: /daftarkan diri/i }).click()
31+
await page.waitForURL('**/dashboard/purchase')
3432
})

e2e/login.spec.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
1-
import { expect } from '@playwright/test'
21
import { readFixture } from '../app/utils/fixtures'
3-
import { test } from './base-test'
2+
import { test, expect } from './base-test'
43

54
test.use({
65
storageState: 'e2e/fixtures/auth/public.json',
76
})
87

9-
test.skip('Login', async ({ page, queries: { getByRole } }) => {
10-
// Go to http://localhost:3000/
8+
test.skip('Login', async ({ page, screen }) => {
9+
// Go to homepage
1110
await page.goto('/')
1211

13-
// Click text=Masuk
14-
await page.click('text=Masuk')
15-
await expect(page).toHaveURL('http://localhost:3000/login')
12+
// Click login link and wait for navigation
13+
await page.getByRole('link', { name: /masuk/i }).click()
14+
await page.waitForURL('**/login')
1615

1716
const { email } = JSON.parse(
1817
await readFixture(`../../e2e/fixtures/users/member.local.json`)
1918
)
2019

21-
// Query email
22-
const emailField = await getByRole('textbox', {
20+
// Query email - using Locator-based screen queries
21+
const emailField = screen.getByRole('textbox', {
2322
name: /alamat email/i,
2423
})
2524

2625
// Fill email
2726
await emailField.fill(email)
2827

29-
// Click text=Kirim link ke alamat email
30-
await Promise.all([
31-
page.waitForNavigation(/*{ url: 'http://localhost:3000/login' }*/),
32-
page.click('text=Kirim link ke alamat email'),
33-
])
28+
// Submit login form
29+
await page.getByRole('button', { name: /kirim link/i }).click()
3430

35-
// Click text=Link telah dikirim ke alamat email Anda
31+
// Wait for success message
3632
await expect(
37-
page.locator('text=Link telah dikirim ke alamat email Anda').first()
33+
page.getByText('Link telah dikirim ke alamat email Anda')
3834
).toBeVisible()
3935

4036
const { magicLink } = JSON.parse(
4137
await readFixture(`../../e2e/fixtures/magic.local.json`)
4238
)
4339

44-
// Go to the magic link
40+
// Go to the magic link and verify dashboard redirect
4541
await page.goto(magicLink)
46-
47-
// If the magic link matches the current token stored in the session storage,
48-
// the user will be redirected to the dashboard automatically.
49-
await expect(page).toHaveURL('http://localhost:3000/dashboard')
42+
await page.waitForURL('**/dashboard')
5043
})

e2e/logout.spec.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
1-
import { expect } from '@playwright/test'
2-
import { test } from './base-test'
1+
import { test, expect } from './base-test'
32

43
test.use({
54
storageState: 'e2e/fixtures/auth/member.local.json',
65
})
76

8-
test('Logout', async ({ page, isMobile, queries: { getByRole } }) => {
9-
// Go to http://localhost:3000/dashboard
7+
test('Logout', async ({ page, isMobile, screen }) => {
108
await page.goto('/dashboard')
119

10+
// Open sidebar on mobile
1211
if (isMobile) {
13-
const openSidebar = await getByRole('button', {
12+
const openSidebar = screen.getByRole('button', {
1413
name: /open sidebar/i,
1514
})
1615
await openSidebar.click()
1716
}
1817

19-
// Click the Keluar button and wait for the redirect to the homepage
20-
const keluarButton = await getByRole('button', { name: /keluar/i })
21-
await Promise.all([
22-
page.waitForNavigation(/*{ url: 'http://localhost:3000' }*/),
23-
keluarButton.click(),
24-
])
18+
// Click the logout button
19+
await screen.getByRole('button', { name: /keluar/i }).click()
2520

26-
// Expect text=Masuk to be visible and linking to the
27-
const loginLink = page.locator('text=Masuk').first()
21+
// Wait for redirect to homepage
22+
await page.waitForURL('/')
23+
24+
// Verify login link is visible
25+
const loginLink = page.getByRole('link', { name: /masuk/i })
2826
await expect(loginLink).toBeVisible()
2927
await expect(loginLink).toHaveAttribute('href', '/login')
3028
})

0 commit comments

Comments
 (0)