Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
advanced/third-party.md
1# Third-Party Service Mocking23## Table of Contents451. [OAuth/SSO Mocking](#oauthsso-mocking)62. [Payment Gateway Mocking](#payment-gateway-mocking)73. [Email Verification](#email-verification)84. [SMS Verification](#sms-verification)95. [Analytics & Tracking](#analytics--tracking)1011## OAuth/SSO Mocking1213### Mock Google OAuth1415```typescript16test("Google OAuth login", async ({ page }) => {17// Mock the OAuth callback18await page.route("**/auth/google/callback**", (route) => {19const url = new URL(route.request().url());20// Simulate successful OAuth by redirecting with token21route.fulfill({22status: 302,23headers: {24Location: "/dashboard?token=mock-jwt-token",25},26});27});2829// Mock the token verification endpoint30await page.route("**/api/auth/verify", (route) =>31route.fulfill({32json: {33valid: true,34user: {35id: "123",36email: "[email protected]",37name: "Test User",38},39},40}),41);4243await page.goto("/login");44await page.getByRole("button", { name: "Sign in with Google" }).click();4546await expect(page.getByText("Welcome, Test User")).toBeVisible();47});48```4950### OAuth Fixture5152```typescript53// fixtures/oauth.fixture.ts54type OAuthProvider = "google" | "github" | "microsoft";5556type OAuthUser = {57id: string;58email: string;59name: string;60avatar?: string;61};6263type OAuthFixtures = {64mockOAuth: (provider: OAuthProvider, user: OAuthUser) => Promise<void>;65};6667export const test = base.extend<OAuthFixtures>({68mockOAuth: async ({ page }, use) => {69await use(async (provider, user) => {70// Mock callback redirect71await page.route(`**/auth/${provider}/callback**`, (route) =>72route.fulfill({73status: 302,74headers: { Location: `/auth/success?provider=${provider}` },75}),76);7778// Mock session/user endpoint79await page.route("**/api/auth/session", (route) =>80route.fulfill({81json: { user, provider, authenticated: true },82}),83);8485// Mock user info endpoint86await page.route("**/api/me", (route) => route.fulfill({ json: user }));87});88},89});9091// Usage92test("login with GitHub", async ({ page, mockOAuth }) => {93await mockOAuth("github", {94id: "gh-123",95email: "[email protected]",96name: "GitHub User",97});9899await page.goto("/login");100await page.getByRole("button", { name: "Sign in with GitHub" }).click();101102await expect(page.getByText("Welcome, GitHub User")).toBeVisible();103});104```105106### Mock SAML SSO107108```typescript109test("SAML SSO login", async ({ page }) => {110// Mock SAML assertion consumer service111await page.route("**/saml/acs", async (route) => {112route.fulfill({113status: 302,114headers: {115Location: "/dashboard",116"Set-Cookie": "session=mock-saml-session; Path=/; HttpOnly",117},118});119});120121// Mock session validation122await page.route("**/api/session", (route) =>123route.fulfill({124json: {125user: { email: "[email protected]", name: "SSO User" },126provider: "saml",127},128}),129);130131await page.goto("/login");132await page.getByRole("button", { name: "SSO Login" }).click();133134await expect(page).toHaveURL("/dashboard");135});136```137138## Payment Gateway Mocking139140### Mock Stripe141142```typescript143test("Stripe checkout", async ({ page }) => {144// Mock Stripe.js145await page.addInitScript(() => {146(window as any).Stripe = () => ({147elements: () => ({148create: () => ({149mount: () => {},150on: () => {},151destroy: () => {},152}),153}),154confirmCardPayment: async () => ({155paymentIntent: { status: "succeeded", id: "pi_mock_123" },156}),157createPaymentMethod: async () => ({158paymentMethod: { id: "pm_mock_123" },159}),160});161});162163// Mock backend payment endpoint164await page.route("**/api/create-payment-intent", (route) =>165route.fulfill({166json: { clientSecret: "pi_mock_123_secret_mock" },167}),168);169170await page.route("**/api/confirm-payment", (route) =>171route.fulfill({172json: { success: true, orderId: "order-123" },173}),174);175176await page.goto("/checkout");177await page.getByRole("button", { name: "Pay $99.99" }).click();178179await expect(page.getByText("Payment successful")).toBeVisible();180});181```182183### Mock PayPal184185```typescript186test("PayPal checkout", async ({ page }) => {187// Mock PayPal SDK188await page.addInitScript(() => {189(window as any).paypal = {190Buttons: () => ({191render: () => Promise.resolve(),192isEligible: () => true,193}),194FUNDING: { PAYPAL: "paypal", CARD: "card" },195};196});197198// Mock PayPal order creation199await page.route("**/api/paypal/create-order", (route) =>200route.fulfill({201json: { orderId: "PAYPAL-ORDER-123" },202}),203);204205// Mock PayPal capture206await page.route("**/api/paypal/capture", (route) =>207route.fulfill({208json: { success: true, transactionId: "TXN-123" },209}),210);211212await page.goto("/checkout");213214// Simulate PayPal approval callback215await page.evaluate(() => {216(window as any).onPayPalApprove?.({ orderID: "PAYPAL-ORDER-123" });217});218219await expect(page.getByText("Order confirmed")).toBeVisible();220});221```222223### Payment Fixture224225```typescript226// fixtures/payment.fixture.ts227type PaymentFixtures = {228mockStripe: (options?: { failPayment?: boolean }) => Promise<void>;229};230231export const test = base.extend<PaymentFixtures>({232mockStripe: async ({ page }, use) => {233await use(async (options = {}) => {234await page.addInitScript(235([shouldFail]) => {236(window as any).Stripe = () => ({237elements: () => ({238create: () => ({239mount: () => {},240on: (event: string, handler: Function) => {241if (event === "ready") setTimeout(handler, 100);242},243destroy: () => {},244}),245}),246confirmCardPayment: async () => {247if (shouldFail) {248return { error: { message: "Card declined" } };249}250return { paymentIntent: { status: "succeeded" } };251},252});253},254[options.failPayment],255);256});257},258});259260// Usage261test("handles declined card", async ({ page, mockStripe }) => {262await mockStripe({ failPayment: true });263264await page.goto("/checkout");265await page.getByRole("button", { name: "Pay" }).click();266267await expect(page.getByText("Card declined")).toBeVisible();268});269```270271## Email Verification272273### Mock Email API274275```typescript276test("email verification flow", async ({ page, request }) => {277let verificationToken: string;278279// Capture the verification email280await page.route("**/api/send-verification", async (route) => {281const body = route.request().postDataJSON();282verificationToken = `mock-token-${Date.now()}`;283284// Don't actually send email, just store token285route.fulfill({286json: { sent: true, messageId: "msg-123" },287});288});289290// Mock token verification291await page.route("**/api/verify-email**", (route) => {292const url = new URL(route.request().url());293const token = url.searchParams.get("token");294295if (token === verificationToken) {296route.fulfill({ json: { verified: true } });297} else {298route.fulfill({ status: 400, json: { error: "Invalid token" } });299}300});301302await page.goto("/signup");303await page.getByLabel("Email").fill("[email protected]");304await page.getByRole("button", { name: "Sign Up" }).click();305306await expect(page.getByText("Check your email")).toBeVisible();307308// Simulate clicking email link309await page.goto(`/verify?token=${verificationToken}`);310311await expect(page.getByText("Email verified")).toBeVisible();312});313```314315### Use Mailinator/Temp Mail316317```typescript318// fixtures/email.fixture.ts319type EmailFixtures = {320getVerificationEmail: (inbox: string) => Promise<{ link: string }>;321};322323export const test = base.extend<EmailFixtures>({324getVerificationEmail: async ({ request }, use) => {325await use(async (inbox) => {326// Poll Mailinator API for new email327const response = await request.get(328`https://api.mailinator.com/v2/domains/public/inboxes/${inbox}`,329{330headers: {331Authorization: `Bearer ${process.env.MAILINATOR_API_KEY}`,332},333},334);335336const messages = await response.json();337const latest = messages.msgs[0];338339// Get full message340const msgResponse = await request.get(341`https://api.mailinator.com/v2/domains/public/inboxes/${inbox}/messages/${latest.id}`,342{343headers: {344Authorization: `Bearer ${process.env.MAILINATOR_API_KEY}`,345},346},347);348349const message = await msgResponse.json();350351// Extract verification link from HTML352const linkMatch = message.parts[0].body.match(353/href="([^"]*verify[^"]*)"/,354);355return { link: linkMatch?.[1] || "" };356});357},358});359```360361## SMS Verification362363### Mock SMS API364365```typescript366test("SMS verification", async ({ page }) => {367let smsCode: string;368369// Capture SMS send370await page.route("**/api/send-sms", (route) => {371smsCode = Math.random().toString().slice(2, 8); // 6-digit code372373route.fulfill({374json: { sent: true, messageId: "sms-123" },375});376});377378// Mock code verification379await page.route("**/api/verify-sms", (route) => {380const body = route.request().postDataJSON();381382if (body.code === smsCode) {383route.fulfill({ json: { verified: true } });384} else {385route.fulfill({ status: 400, json: { error: "Invalid code" } });386}387});388389await page.goto("/verify-phone");390await page.getByLabel("Phone").fill("+1234567890");391await page.getByRole("button", { name: "Send Code" }).click();392393// Enter the code394await page.getByLabel("Verification Code").fill(smsCode);395await page.getByRole("button", { name: "Verify" }).click();396397await expect(page.getByText("Phone verified")).toBeVisible();398});399```400401## Analytics & Tracking402403### Block Analytics in Tests404405```typescript406test.beforeEach(async ({ page }) => {407// Block all analytics/tracking408await page.route(409/google-analytics|googletagmanager|facebook|hotjar|segment|mixpanel|amplitude/,410(route) => route.abort(),411);412});413```414415### Mock Analytics for Verification416417```typescript418test("tracks purchase event", async ({ page }) => {419const analyticsEvents: any[] = [];420421// Capture analytics calls422await page.route("**/api/analytics/**", (route) => {423analyticsEvents.push(route.request().postDataJSON());424route.fulfill({ status: 200 });425});426427// Mock analytics SDK428await page.addInitScript(() => {429(window as any).analytics = {430track: (event: string, props: any) => {431fetch("/api/analytics/track", {432method: "POST",433body: JSON.stringify({ event, props }),434});435},436};437});438439await page.goto("/checkout");440await page.getByRole("button", { name: "Complete Purchase" }).click();441442// Verify analytics event was sent443expect(analyticsEvents).toContainEqual(444expect.objectContaining({445event: "Purchase Completed",446props: expect.objectContaining({ amount: expect.any(Number) }),447}),448);449});450```451452## Anti-Patterns to Avoid453454| Anti-Pattern | Problem | Solution |455| ------------------------- | ------------------------------ | ----------------------- |456| Using real OAuth in tests | Slow, needs credentials, flaky | Mock OAuth endpoints |457| Real payment processing | Charges real money, slow | Use test mode or mock |458| Waiting for real emails | Very slow, unreliable | Mock email API |459| Not mocking analytics | Pollutes analytics data | Block or mock analytics |460461## Related References462463- **Network Mocking**: See [network-advanced.md](network-advanced.md) for route patterns464- **Authentication**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for auth patterns465