Complete Claude Code plugin with: - 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report) - 3 specialized agents (test-architect, test-debugger, migration-planner) - 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility) - TestRail MCP server (TypeScript) — 8 tools for bidirectional sync - BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing - Smart hooks (auto-validate tests, auto-detect Playwright projects) - 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests) - Leverages Claude Code built-ins (/batch, /debug, Explore subagent) - Zero-config for core features; TestRail/BrowserStack via env vars - Both TypeScript and JavaScript support throughout Co-authored-by: Leo <leo@openclaw.ai>
119 lines
5.0 KiB
Markdown
119 lines
5.0 KiB
Markdown
# Email Verification Template
|
|
|
|
Tests email verification link, resend flow, and expired token handling.
|
|
|
|
## Prerequisites
|
|
- Registered but unverified account: `{{unverifiedEmail}}`
|
|
- Valid token: `{{verificationToken}}`
|
|
- Expired token: `{{expiredVerificationToken}}`
|
|
- App running at `{{baseUrl}}`
|
|
|
|
---
|
|
|
|
## TypeScript
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Email Verification', () => {
|
|
// Happy path: valid verification link
|
|
test('verifies email with valid token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{verificationToken}}');
|
|
await expect(page.getByRole('heading', { name: /email verified|verified/i })).toBeVisible();
|
|
await expect(page.getByRole('link', { name: /continue|go to dashboard/i })).toBeVisible();
|
|
});
|
|
|
|
// Happy path: continues to app after verification
|
|
test('redirects to dashboard after clicking continue', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{verificationToken}}');
|
|
await page.getByRole('link', { name: /continue|go to dashboard/i }).click();
|
|
await expect(page).toHaveURL('{{baseUrl}}/dashboard');
|
|
});
|
|
|
|
// Happy path: resend verification email
|
|
test('resends verification email', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email/resend');
|
|
await page.getByRole('textbox', { name: /email/i }).fill('{{unverifiedEmail}}');
|
|
await page.getByRole('button', { name: /resend/i }).click();
|
|
await expect(page.getByRole('alert')).toContainText(/sent|check your email/i);
|
|
});
|
|
|
|
// Happy path: verification prompt on login for unverified user
|
|
test('shows verification prompt when unverified user logs in', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/login');
|
|
await page.getByRole('textbox', { name: /email/i }).fill('{{unverifiedEmail}}');
|
|
await page.getByRole('textbox', { name: /password/i }).fill('{{password}}');
|
|
await page.getByRole('button', { name: /sign in/i }).click();
|
|
await expect(page.getByText(/verify.*email|check.*inbox/i)).toBeVisible();
|
|
await expect(page.getByRole('button', { name: /resend.*verification/i })).toBeVisible();
|
|
});
|
|
|
|
// Error case: expired token
|
|
test('shows error for expired verification token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{expiredVerificationToken}}');
|
|
await expect(page.getByRole('heading', { name: /link.*expired|verification.*failed/i })).toBeVisible();
|
|
await expect(page.getByRole('link', { name: /resend|request new/i })).toBeVisible();
|
|
});
|
|
|
|
// Error case: invalid token
|
|
test('shows error for invalid verification token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token=invalid-token-xyz');
|
|
await expect(page.getByRole('heading', { name: /invalid|failed/i })).toBeVisible();
|
|
});
|
|
|
|
// Edge case: already verified user hitting link
|
|
test('shows already verified message for used token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{usedVerificationToken}}');
|
|
await expect(page.getByText(/already verified|email.*confirmed/i)).toBeVisible();
|
|
await expect(page.getByRole('link', { name: /sign in/i })).toBeVisible();
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript
|
|
|
|
```javascript
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
test.describe('Email Verification', () => {
|
|
test('verifies email with valid token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{verificationToken}}');
|
|
await expect(page.getByRole('heading', { name: /email verified/i })).toBeVisible();
|
|
});
|
|
|
|
test('shows error for expired token', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email?token={{expiredVerificationToken}}');
|
|
await expect(page.getByRole('heading', { name: /link.*expired/i })).toBeVisible();
|
|
await expect(page.getByRole('link', { name: /resend|request new/i })).toBeVisible();
|
|
});
|
|
|
|
test('resends verification email', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/verify-email/resend');
|
|
await page.getByRole('textbox', { name: /email/i }).fill('{{unverifiedEmail}}');
|
|
await page.getByRole('button', { name: /resend/i }).click();
|
|
await expect(page.getByRole('alert')).toContainText(/sent/i);
|
|
});
|
|
|
|
test('shows verification prompt on login for unverified user', async ({ page }) => {
|
|
await page.goto('{{baseUrl}}/login');
|
|
await page.getByRole('textbox', { name: /email/i }).fill('{{unverifiedEmail}}');
|
|
await page.getByRole('textbox', { name: /password/i }).fill('{{password}}');
|
|
await page.getByRole('button', { name: /sign in/i }).click();
|
|
await expect(page.getByText(/verify.*email/i)).toBeVisible();
|
|
});
|
|
});
|
|
```
|
|
|
|
## Variants
|
|
| Variant | Description |
|
|
|---------|-------------|
|
|
| Valid token | Email verified heading + continue link |
|
|
| Continue CTA | Navigates to dashboard |
|
|
| Resend | Sends new email, success alert |
|
|
| Login prompt | Unverified login shows resend button |
|
|
| Expired token | Error heading + resend link |
|
|
| Invalid token | Generic error heading |
|
|
| Already verified | "Already verified" with login link |
|