Fix: legit logins only
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
# Test Suite Structure
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Configuration](#configuration)
|
||||
2. [E2E Tests](#e2e-tests)
|
||||
3. [Component Tests](#component-tests)
|
||||
4. [API Tests](#api-tests)
|
||||
5. [Visual Regression Tests](#visual-regression-tests)
|
||||
6. [Directory Structure](#directory-structure)
|
||||
7. [Tagging & Filtering](#tagging--filtering)
|
||||
|
||||
### Project Setup
|
||||
|
||||
```bash
|
||||
npm init playwright@latest
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Essential Configuration
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: [["html"], ["list"]],
|
||||
use: {
|
||||
baseURL: "http://localhost:3000",
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
projects: [
|
||||
{ name: "setup", testMatch: /.*\.setup\.ts/ },
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: "npm run dev",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## E2E Tests
|
||||
|
||||
Full user journey tests through the browser.
|
||||
|
||||
### Structure
|
||||
|
||||
```typescript
|
||||
// tests/e2e/checkout.spec.ts
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Checkout Flow", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/products");
|
||||
});
|
||||
|
||||
test("complete purchase as guest", async ({ page }) => {
|
||||
// Add to cart
|
||||
await page.getByRole("button", { name: "Add to Cart" }).first().click();
|
||||
await expect(page.getByTestId("cart-count")).toHaveText("1");
|
||||
|
||||
// Go to checkout
|
||||
await page.getByRole("link", { name: "Cart" }).click();
|
||||
await page.getByRole("button", { name: "Checkout" }).click();
|
||||
|
||||
// Fill shipping
|
||||
await page.getByLabel("Email").fill("guest@example.com");
|
||||
await page.getByLabel("Address").fill("123 Test St");
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
// Payment
|
||||
await page.getByLabel("Card Number").fill("4242424242424242");
|
||||
await page.getByRole("button", { name: "Pay Now" }).click();
|
||||
|
||||
// Confirmation
|
||||
await expect(page.getByRole("heading")).toHaveText("Order Confirmed");
|
||||
});
|
||||
|
||||
test("apply discount code", async ({ page }) => {
|
||||
await page.getByRole("button", { name: "Add to Cart" }).first().click();
|
||||
await page.getByRole("link", { name: "Cart" }).click();
|
||||
|
||||
await page.getByLabel("Discount Code").fill("SAVE10");
|
||||
await page.getByRole("button", { name: "Apply" }).click();
|
||||
|
||||
await expect(page.getByText("10% discount applied")).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Test critical user journeys
|
||||
- Keep tests independent
|
||||
- Use realistic data
|
||||
- Clean up test data in teardown
|
||||
|
||||
## Component Tests
|
||||
|
||||
Test individual components in isolation using Playwright Component Testing.
|
||||
|
||||
```bash
|
||||
npm init playwright@latest -- --ct
|
||||
```
|
||||
|
||||
For comprehensive component testing patterns including mounting, props, events, slots, mocking, and framework-specific examples (React, Vue, Svelte), see **[component-testing.md](../testing-patterns/component-testing.md)**.
|
||||
|
||||
## API Tests
|
||||
|
||||
Test backend APIs without browser.
|
||||
|
||||
### API Mocking Patterns
|
||||
|
||||
For E2E tests that need to mock API responses:
|
||||
|
||||
```typescript
|
||||
// Mock single endpoint
|
||||
test("displays mocked users", async ({ page }) => {
|
||||
await page.route("**/api/users", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
json: [{ id: 1, name: "Test User" }],
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto("/users");
|
||||
await expect(page.getByText("Test User")).toBeVisible();
|
||||
});
|
||||
|
||||
// Mock with different responses
|
||||
test("handles API errors", async ({ page }) => {
|
||||
await page.route("**/api/users", (route) =>
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
json: { error: "Server error" },
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto("/users");
|
||||
await expect(page.getByText("Server error")).toBeVisible();
|
||||
});
|
||||
|
||||
// Conditional mocking
|
||||
test("mocks based on request", async ({ page }) => {
|
||||
await page.route("**/api/users", (route, request) => {
|
||||
if (request.method() === "GET") {
|
||||
route.fulfill({ json: [{ id: 1, name: "User" }] });
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Mock with delay (simulate slow network)
|
||||
test("handles slow API", async ({ page }) => {
|
||||
await page.route("**/api/data", (route) =>
|
||||
route.fulfill({
|
||||
json: { data: "test" },
|
||||
delay: 2000, // 2 second delay
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto("/dashboard");
|
||||
await expect(page.getByText("Loading...")).toBeVisible();
|
||||
await expect(page.getByText("test")).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
For advanced patterns (GraphQL mocking, HAR recording, request modification, network throttling), see **[network-advanced.md](../advanced/network-advanced.md)**.
|
||||
|
||||
## Visual Regression Tests
|
||||
|
||||
Compare screenshots to detect visual changes.
|
||||
|
||||
### Basic Visual Test
|
||||
|
||||
```typescript
|
||||
// tests/visual/homepage.spec.ts
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("homepage visual", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page).toHaveScreenshot("homepage.png");
|
||||
});
|
||||
|
||||
test("component visual", async ({ page }) => {
|
||||
await page.goto("/components");
|
||||
|
||||
const button = page.getByRole("button", { name: "Primary" });
|
||||
await expect(button).toHaveScreenshot("primary-button.png");
|
||||
});
|
||||
```
|
||||
|
||||
### Visual Test Options
|
||||
|
||||
```typescript
|
||||
test("dashboard visual", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
|
||||
await expect(page).toHaveScreenshot("dashboard.png", {
|
||||
fullPage: true, // Capture entire scrollable page
|
||||
maxDiffPixels: 100, // Allow up to 100 different pixels
|
||||
maxDiffPixelRatio: 0.01, // Or 1% difference
|
||||
threshold: 0.2, // Pixel comparison threshold
|
||||
animations: "disabled", // Disable animations
|
||||
mask: [page.getByTestId("date")], // Mask dynamic content
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Handling Dynamic Content
|
||||
|
||||
```typescript
|
||||
test("page with dynamic content", async ({ page }) => {
|
||||
await page.goto("/profile");
|
||||
|
||||
// Mask elements that change
|
||||
await expect(page).toHaveScreenshot("profile.png", {
|
||||
mask: [
|
||||
page.getByTestId("timestamp"),
|
||||
page.getByTestId("avatar"),
|
||||
page.getByRole("img"),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// Or hide elements via CSS
|
||||
test("page hiding dynamic elements", async ({ page }) => {
|
||||
await page.goto("/profile");
|
||||
|
||||
await page.addStyleTag({
|
||||
content: `
|
||||
.dynamic-content { visibility: hidden !important; }
|
||||
[data-testid="ad-banner"] { display: none !important; }
|
||||
`,
|
||||
});
|
||||
|
||||
await expect(page).toHaveScreenshot("profile-stable.png");
|
||||
});
|
||||
```
|
||||
|
||||
### Visual Test Configuration
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
export default defineConfig({
|
||||
expect: {
|
||||
toHaveScreenshot: {
|
||||
maxDiffPixels: 50,
|
||||
animations: "disabled",
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "visual-chrome",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
viewport: { width: 1280, height: 720 },
|
||||
},
|
||||
testMatch: /.*visual.*\.spec\.ts/,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Update Snapshots
|
||||
|
||||
```bash
|
||||
# Update all snapshots
|
||||
npx playwright test --update-snapshots
|
||||
|
||||
# Update specific test
|
||||
npx playwright test homepage.spec.ts --update-snapshots
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── e2e/ # End-to-end tests
|
||||
│ ├── auth.spec.ts
|
||||
│ ├── checkout.spec.ts
|
||||
│ └── dashboard.spec.ts
|
||||
├── component/ # Component tests
|
||||
│ ├── Button.spec.tsx
|
||||
│ └── Modal.spec.tsx
|
||||
├── api/ # API tests
|
||||
│ ├── users.spec.ts
|
||||
│ └── products.spec.ts
|
||||
├── visual/ # Visual regression tests
|
||||
│ └── homepage.spec.ts
|
||||
├── fixtures/ # Custom fixtures
|
||||
│ ├── auth.fixture.ts
|
||||
│ └── api.fixture.ts
|
||||
└── pages/ # Page objects
|
||||
├── login.page.ts
|
||||
└── dashboard.page.ts
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
| Anti-Pattern | Problem | Solution |
|
||||
| ------------------------------------- | ---------------------------------- | ------------------------- |
|
||||
| Long test files | Hard to maintain, slow to navigate | Split by feature, use POM |
|
||||
| Tests depend on execution order | Flaky, hard to debug | Keep tests independent |
|
||||
| Testing multiple features in one test | Hard to debug failures | One feature per test |
|
||||
|
||||
## Related References
|
||||
|
||||
- **Component Testing**: See [component-testing.md](../testing-patterns/component-testing.md) for comprehensive CT patterns
|
||||
- **Projects**: See [projects-dependencies.md](projects-dependencies.md) for project-based filtering
|
||||
- **Page Objects**: See [page-object-model.md](page-object-model.md) for organizing page interactions
|
||||
- **Test Data**: See [fixtures-hooks.md](fixtures-hooks.md) for managing test data
|
||||
|
||||
## Tagging & Filtering
|
||||
|
||||
### Using Tags
|
||||
|
||||
```typescript
|
||||
test("user login @smoke @auth", async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test("checkout flow @e2e @critical", async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test.describe("API tests @api", () => {
|
||||
test("create user", async ({ request }) => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Running Tagged Tests
|
||||
|
||||
```bash
|
||||
# Run smoke tests
|
||||
npx playwright test --grep @smoke
|
||||
|
||||
# Run all except slow tests
|
||||
npx playwright test --grep-invert @slow
|
||||
|
||||
# Combine tags
|
||||
npx playwright test --grep "@smoke|@critical"
|
||||
```
|
||||
|
||||
For project-based filtering and advanced project configuration, see **[projects-dependencies.md](projects-dependencies.md)**.
|
||||
Reference in New Issue
Block a user