.agents/skills/playwright-best-practices/advanced/third-party.md
test('Google OAuth login', async ({page}) => {
// Mock the OAuth callback
await page.route('**/auth/google/callback**', (route) => {
const url = new URL(route.request().url())
// Simulate successful OAuth by redirecting with token
route.fulfill({
status: 302,
headers: {
Location: '/dashboard?token=mock-jwt-token',
},
})
})
// Mock the token verification endpoint
await page.route('**/api/auth/verify', (route) =>
route.fulfill({
json: {
valid: true,
user: {
id: '123',
email: '[email protected]',
name: 'Test User',
},
},
}),
)
await page.goto('/login')
await page.getByRole('button', {name: 'Sign in with Google'}).click()
await expect(page.getByText('Welcome, Test User')).toBeVisible()
})
// fixtures/oauth.fixture.ts
type OAuthProvider = 'google' | 'github' | 'microsoft'
type OAuthUser = {
id: string
email: string
name: string
avatar?: string
}
type OAuthFixtures = {
mockOAuth: (provider: OAuthProvider, user: OAuthUser) => Promise<void>
}
export const test = base.extend<OAuthFixtures>({
mockOAuth: async ({page}, use) => {
await use(async (provider, user) => {
// Mock callback redirect
await page.route(`**/auth/${provider}/callback**`, (route) =>
route.fulfill({
status: 302,
headers: {Location: `/auth/success?provider=${provider}`},
}),
)
// Mock session/user endpoint
await page.route('**/api/auth/session', (route) =>
route.fulfill({
json: {user, provider, authenticated: true},
}),
)
// Mock user info endpoint
await page.route('**/api/me', (route) => route.fulfill({json: user}))
})
},
})
// Usage
test('login with GitHub', async ({page, mockOAuth}) => {
await mockOAuth('github', {
id: 'gh-123',
email: '[email protected]',
name: 'GitHub User',
})
await page.goto('/login')
await page.getByRole('button', {name: 'Sign in with GitHub'}).click()
await expect(page.getByText('Welcome, GitHub User')).toBeVisible()
})
test('SAML SSO login', async ({page}) => {
// Mock SAML assertion consumer service
await page.route('**/saml/acs', async (route) => {
route.fulfill({
status: 302,
headers: {
'Location': '/dashboard',
'Set-Cookie': 'session=mock-saml-session; Path=/; HttpOnly',
},
})
})
// Mock session validation
await page.route('**/api/session', (route) =>
route.fulfill({
json: {
user: {email: '[email protected]', name: 'SSO User'},
provider: 'saml',
},
}),
)
await page.goto('/login')
await page.getByRole('button', {name: 'SSO Login'}).click()
await expect(page).toHaveURL('/dashboard')
})
test('Stripe checkout', async ({page}) => {
// Mock Stripe.js
await page.addInitScript(() => {
;(window as any).Stripe = () => ({
elements: () => ({
create: () => ({
mount: () => {},
on: () => {},
destroy: () => {},
}),
}),
confirmCardPayment: async () => ({
paymentIntent: {status: 'succeeded', id: 'pi_mock_123'},
}),
createPaymentMethod: async () => ({
paymentMethod: {id: 'pm_mock_123'},
}),
})
})
// Mock backend payment endpoint
await page.route('**/api/create-payment-intent', (route) =>
route.fulfill({
json: {clientSecret: 'pi_mock_123_secret_mock'},
}),
)
await page.route('**/api/confirm-payment', (route) =>
route.fulfill({
json: {success: true, orderId: 'order-123'},
}),
)
await page.goto('/checkout')
await page.getByRole('button', {name: 'Pay $99.99'}).click()
await expect(page.getByText('Payment successful')).toBeVisible()
})
test('PayPal checkout', async ({page}) => {
// Mock PayPal SDK
await page.addInitScript(() => {
;(window as any).paypal = {
Buttons: () => ({
render: () => Promise.resolve(),
isEligible: () => true,
}),
FUNDING: {PAYPAL: 'paypal', CARD: 'card'},
}
})
// Mock PayPal order creation
await page.route('**/api/paypal/create-order', (route) =>
route.fulfill({
json: {orderId: 'PAYPAL-ORDER-123'},
}),
)
// Mock PayPal capture
await page.route('**/api/paypal/capture', (route) =>
route.fulfill({
json: {success: true, transactionId: 'TXN-123'},
}),
)
await page.goto('/checkout')
// Simulate PayPal approval callback
await page.evaluate(() => {
;(window as any).onPayPalApprove?.({orderID: 'PAYPAL-ORDER-123'})
})
await expect(page.getByText('Order confirmed')).toBeVisible()
})
// fixtures/payment.fixture.ts
type PaymentFixtures = {
mockStripe: (options?: {failPayment?: boolean}) => Promise<void>
}
export const test = base.extend<PaymentFixtures>({
mockStripe: async ({page}, use) => {
await use(async (options = {}) => {
await page.addInitScript(
([shouldFail]) => {
;(window as any).Stripe = () => ({
elements: () => ({
create: () => ({
mount: () => {},
on: (event: string, handler: Function) => {
if (event === 'ready') setTimeout(handler, 100)
},
destroy: () => {},
}),
}),
confirmCardPayment: async () => {
if (shouldFail) {
return {error: {message: 'Card declined'}}
}
return {paymentIntent: {status: 'succeeded'}}
},
})
},
[options.failPayment],
)
})
},
})
// Usage
test('handles declined card', async ({page, mockStripe}) => {
await mockStripe({failPayment: true})
await page.goto('/checkout')
await page.getByRole('button', {name: 'Pay'}).click()
await expect(page.getByText('Card declined')).toBeVisible()
})
test('email verification flow', async ({page, request}) => {
let verificationToken: string
// Capture the verification email
await page.route('**/api/send-verification', async (route) => {
const body = route.request().postDataJSON()
verificationToken = `mock-token-${Date.now()}`
// Don't actually send email, just store token
route.fulfill({
json: {sent: true, messageId: 'msg-123'},
})
})
// Mock token verification
await page.route('**/api/verify-email**', (route) => {
const url = new URL(route.request().url())
const token = url.searchParams.get('token')
if (token === verificationToken) {
route.fulfill({json: {verified: true}})
} else {
route.fulfill({status: 400, json: {error: 'Invalid token'}})
}
})
await page.goto('/signup')
await page.getByLabel('Email').fill('[email protected]')
await page.getByRole('button', {name: 'Sign Up'}).click()
await expect(page.getByText('Check your email')).toBeVisible()
// Simulate clicking email link
await page.goto(`/verify?token=${verificationToken}`)
await expect(page.getByText('Email verified')).toBeVisible()
})
// fixtures/email.fixture.ts
type EmailFixtures = {
getVerificationEmail: (inbox: string) => Promise<{link: string}>
}
export const test = base.extend<EmailFixtures>({
getVerificationEmail: async ({request}, use) => {
await use(async (inbox) => {
// Poll Mailinator API for new email
const response = await request.get(
`https://api.mailinator.com/v2/domains/public/inboxes/${inbox}`,
{
headers: {
Authorization: `Bearer ${process.env.MAILINATOR_API_KEY}`,
},
},
)
const messages = await response.json()
const latest = messages.msgs[0]
// Get full message
const msgResponse = await request.get(
`https://api.mailinator.com/v2/domains/public/inboxes/${inbox}/messages/${latest.id}`,
{
headers: {
Authorization: `Bearer ${process.env.MAILINATOR_API_KEY}`,
},
},
)
const message = await msgResponse.json()
// Extract verification link from HTML
const linkMatch = message.parts[0].body.match(/href="([^"]*verify[^"]*)"/)
return {link: linkMatch?.[1] || ''}
})
},
})
test('SMS verification', async ({page}) => {
let smsCode: string
// Capture SMS send
await page.route('**/api/send-sms', (route) => {
smsCode = Math.random().toString().slice(2, 8) // 6-digit code
route.fulfill({
json: {sent: true, messageId: 'sms-123'},
})
})
// Mock code verification
await page.route('**/api/verify-sms', (route) => {
const body = route.request().postDataJSON()
if (body.code === smsCode) {
route.fulfill({json: {verified: true}})
} else {
route.fulfill({status: 400, json: {error: 'Invalid code'}})
}
})
await page.goto('/verify-phone')
await page.getByLabel('Phone').fill('+1234567890')
await page.getByRole('button', {name: 'Send Code'}).click()
// Enter the code
await page.getByLabel('Verification Code').fill(smsCode)
await page.getByRole('button', {name: 'Verify'}).click()
await expect(page.getByText('Phone verified')).toBeVisible()
})
test.beforeEach(async ({page}) => {
// Block all analytics/tracking
await page.route(
/google-analytics|googletagmanager|facebook|hotjar|segment|mixpanel|amplitude/,
(route) => route.abort(),
)
})
test('tracks purchase event', async ({page}) => {
const analyticsEvents: any[] = []
// Capture analytics calls
await page.route('**/api/analytics/**', (route) => {
analyticsEvents.push(route.request().postDataJSON())
route.fulfill({status: 200})
})
// Mock analytics SDK
await page.addInitScript(() => {
;(window as any).analytics = {
track: (event: string, props: any) => {
fetch('/api/analytics/track', {
method: 'POST',
body: JSON.stringify({event, props}),
})
},
}
})
await page.goto('/checkout')
await page.getByRole('button', {name: 'Complete Purchase'}).click()
// Verify analytics event was sent
expect(analyticsEvents).toContainEqual(
expect.objectContaining({
event: 'Purchase Completed',
props: expect.objectContaining({amount: expect.any(Number)}),
}),
)
})
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Using real OAuth in tests | Slow, needs credentials, flaky | Mock OAuth endpoints |
| Real payment processing | Charges real money, slow | Use test mode or mock |
| Waiting for real emails | Very slow, unreliable | Mock email API |
| Not mocking analytics | Pollutes analytics data | Block or mock analytics |