SurfSense/.cursor/skills/playwright-testing/advanced/third-party.md
2026-05-10 04:19:55 +05:30

464 lines
12 KiB
Markdown

# Third-Party Service Mocking
## Table of Contents
1. [OAuth/SSO Mocking](#oauthsso-mocking)
2. [Payment Gateway Mocking](#payment-gateway-mocking)
3. [Email Verification](#email-verification)
4. [SMS Verification](#sms-verification)
5. [Analytics & Tracking](#analytics--tracking)
## OAuth/SSO Mocking
### Mock Google OAuth
```typescript
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: "test@gmail.com",
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();
});
```
### OAuth Fixture
```typescript
// 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: "dev@github.com",
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();
});
```
### Mock SAML SSO
```typescript
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: "user@company.com", name: "SSO User" },
provider: "saml",
},
}),
);
await page.goto("/login");
await page.getByRole("button", { name: "SSO Login" }).click();
await expect(page).toHaveURL("/dashboard");
});
```
## Payment Gateway Mocking
### Mock Stripe
```typescript
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();
});
```
### Mock PayPal
```typescript
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();
});
```
### Payment Fixture
```typescript
// 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();
});
```
## Email Verification
### Mock Email API
```typescript
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("test@example.com");
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();
});
```
### Use Mailinator/Temp Mail
```typescript
// 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] || "" };
});
},
});
```
## SMS Verification
### Mock SMS API
```typescript
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();
});
```
## Analytics & Tracking
### Block Analytics in Tests
```typescript
test.beforeEach(async ({ page }) => {
// Block all analytics/tracking
await page.route(
/google-analytics|googletagmanager|facebook|hotjar|segment|mixpanel|amplitude/,
(route) => route.abort(),
);
});
```
### Mock Analytics for Verification
```typescript
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-Patterns to Avoid
| 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 |
## Related References
- **Network Mocking**: See [network-advanced.md](network-advanced.md) for route patterns
- **Authentication**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for auth patterns