test-automation-framework

安装量: 147
排名: #5842

安装

npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill test-automation-framework

Test Automation Framework Overview

A test automation framework provides structure, reusability, and maintainability for automated tests. It defines patterns for organizing tests, managing test data, handling dependencies, and generating reports. A well-designed framework reduces duplication, improves reliability, and accelerates test development.

When to Use Setting up new test automation Scaling existing test suites Standardizing test practices across teams Reducing test maintenance burden Improving test reliability and speed Organizing large test codebases Implementing reusable test utilities Creating consistent reporting Framework Components Test Organization: Structure and hierarchy Page Objects: UI element abstraction Test Data Management: Fixtures and factories Configuration: Environment-specific settings Utilities: Shared helpers and functions Reporting: Test results and metrics CI/CD Integration: Automated execution Instructions 1. Page Object Model (Playwright/TypeScript) // framework/pages/BasePage.ts import { Page, Locator } from '@playwright/test';

export abstract class BasePage { constructor(protected page: Page) {}

async goto(path: string) { await this.page.goto(path); }

async waitForPageLoad() { await this.page.waitForLoadState('networkidle'); }

async takeScreenshot(name: string) { await this.page.screenshot({ path: screenshots/${name}.png }); }

protected async clickAndWait(locator: Locator) { await Promise.all([ this.page.waitForResponse(resp => resp.status() === 200), locator.click() ]); } }

// framework/pages/LoginPage.ts export class LoginPage extends BasePage { // Locators private readonly emailInput = this.page.locator('[name="email"]'); private readonly passwordInput = this.page.locator('[name="password"]'); private readonly submitButton = this.page.locator('button[type="submit"]'); private readonly errorMessage = this.page.locator('.error-message');

async goto() { await super.goto('/login'); }

async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); }

async loginWithValidation(email: string, password: string) { await this.login(email, password); await this.page.waitForURL('/dashboard'); }

async getErrorMessage(): Promise { return await this.errorMessage.textContent() || ''; }

async isLoggedIn(): Promise { return this.page.url().includes('/dashboard'); } }

// framework/pages/ProductPage.ts export class ProductPage extends BasePage { private readonly addToCartButton = this.page.locator('[data-testid="add-to-cart"]'); private readonly quantityInput = this.page.locator('[name="quantity"]'); private readonly priceLabel = this.page.locator('.price');

async goto(productId: string) { await super.goto(/products/${productId}); }

async addToCart(quantity: number = 1) { if (quantity > 1) { await this.quantityInput.fill(String(quantity)); } await this.addToCartButton.click(); }

async getPrice(): Promise { const priceText = await this.priceLabel.textContent(); return parseFloat(priceText?.replace(/[^0-9.]/g, '') || '0'); } }

// tests/checkout.test.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../framework/pages/LoginPage'; import { ProductPage } from '../framework/pages/ProductPage'; import { CartPage } from '../framework/pages/CartPage'; import { CheckoutPage } from '../framework/pages/CheckoutPage';

test.describe('Checkout Flow', () => { let loginPage: LoginPage; let productPage: ProductPage; let cartPage: CartPage; let checkoutPage: CheckoutPage;

test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); productPage = new ProductPage(page); cartPage = new CartPage(page); checkoutPage = new CheckoutPage(page);

await loginPage.goto();
await loginPage.loginWithValidation('user@test.com', 'password123');

});

test('complete checkout process', async () => { // Add product to cart await productPage.goto('product-1'); await productPage.addToCart(2);

// Verify cart
await cartPage.goto();
expect(await cartPage.getItemCount()).toBe(2);

// Checkout
await checkoutPage.goto();
await checkoutPage.fillShippingInfo({
  name: 'John Doe',
  address: '123 Main St',
  city: 'San Francisco',
  zip: '94105'
});

await checkoutPage.fillPaymentInfo({
  cardNumber: '4242424242424242',
  expiry: '12/25',
  cvc: '123'
});

await checkoutPage.placeOrder();

expect(await checkoutPage.isOrderConfirmed()).toBe(true);

}); });

  1. Test Fixtures and Factories // framework/fixtures/database.ts import { test as base } from '@playwright/test'; import { PrismaClient } from '@prisma/client';

export const test = base.extend<{ db: PrismaClient; testUser: User; cleanupData: () => Promise; }>({ db: async ({}, use) => { const db = new PrismaClient(); await use(db); await db.$disconnect(); },

testUser: async ({ db }, use) => { const user = await db.user.create({ data: { email: test-${Date.now()}@example.com, name: 'Test User', password: await hashPassword('password123'), }, }); await use(user); await db.user.delete({ where: { id: user.id } }); },

cleanupData: async ({ db }, use) => { const cleanup = async () => { await db.order.deleteMany({}); await db.product.deleteMany({}); }; await use(cleanup); }, });

export { expect } from '@playwright/test';

// Usage in tests import { test, expect } from '../framework/fixtures/database';

test('user can create order', async ({ db, testUser }) => { const product = await db.product.create({ data: { name: 'Test Product', price: 99.99 } });

const order = await db.order.create({ data: { userId: testUser.id, items: { create: [{ productId: product.id, quantity: 1 }] } } });

expect(order.userId).toBe(testUser.id); });

  1. Custom Test Utilities // framework/utils/helpers.ts import { Page, expect } from '@playwright/test';

export class TestHelpers { static async waitForAPIResponse( page: Page, urlPattern: string | RegExp, action: () => Promise ) { const responsePromise = page.waitForResponse(urlPattern); await action(); return await responsePromise; }

static async mockAPIResponse( page: Page, url: string | RegExp, response: any, status: number = 200 ) { await page.route(url, route => { route.fulfill({ status, contentType: 'application/json', body: JSON.stringify(response), }); }); }

static async fillForm(page: Page, formData: Record) { for (const [name, value] of Object.entries(formData)) { await page.fill([name="${name}"], value); } }

static generateTestEmail(): string { return test-${Date.now()}-${Math.random().toString(36)}@example.com; }

static async verifyToastMessage(page: Page, message: string) { const toast = page.locator('.toast-message'); await expect(toast).toContainText(message); await expect(toast).toBeVisible(); } }

// Usage import { TestHelpers } from '../framework/utils/helpers';

test('form submission', async ({ page }) => { await page.goto('/contact');

await TestHelpers.fillForm(page, { name: 'John Doe', email: TestHelpers.generateTestEmail(), message: 'Test message' });

await page.click('button[type="submit"]');

await TestHelpers.verifyToastMessage(page, 'Message sent successfully'); });

  1. Configuration Management // framework/config/config.ts import * as dotenv from 'dotenv';

dotenv.config();

export interface TestConfig { baseUrl: string; apiUrl: string; timeout: number; headless: boolean; slowMo: number; screenshots: boolean; video: boolean; testUser: { email: string; password: string; }; }

const environments: Record = { development: { baseUrl: 'http://localhost:3000', apiUrl: 'http://localhost:3001', timeout: 30000, headless: false, slowMo: 0, screenshots: true, video: false, testUser: { email: 'dev@test.com', password: 'devpass123', }, }, staging: { baseUrl: 'https://staging.example.com', apiUrl: 'https://api-staging.example.com', timeout: 60000, headless: true, slowMo: 0, screenshots: true, video: true, testUser: { email: process.env.STAGING_USER_EMAIL!, password: process.env.STAGING_USER_PASSWORD!, }, }, production: { baseUrl: 'https://example.com', apiUrl: 'https://api.example.com', timeout: 60000, headless: true, slowMo: 100, screenshots: true, video: true, testUser: { email: process.env.PROD_USER_EMAIL!, password: process.env.PROD_USER_PASSWORD!, }, }, };

export const config: TestConfig = environments[process.env.TEST_ENV || 'development'];

  1. Custom Reporter // framework/reporters/CustomReporter.ts import { Reporter, TestCase, TestResult } from '@playwright/test/reporter';

class CustomReporter implements Reporter { private stats = { passed: 0, failed: 0, skipped: 0, total: 0, };

onBegin() { console.log('Starting test run...'); }

onTestEnd(test: TestCase, result: TestResult) { this.stats.total++;

if (result.status === 'passed') {
  this.stats.passed++;
  console.log(`✓ ${test.title}`);
} else if (result.status === 'failed') {
  this.stats.failed++;
  console.log(`✗ ${test.title}`);
  console.log(`  Error: ${result.error?.message}`);
} else if (result.status === 'skipped') {
  this.stats.skipped++;
  console.log(`⊘ ${test.title}`);
}

}

onEnd() { console.log('\nTest Summary:'); console.log(Total: ${this.stats.total}); console.log(Passed: ${this.stats.passed}); console.log(Failed: ${this.stats.failed}); console.log(Skipped: ${this.stats.skipped}); } }

export default CustomReporter;

  1. pytest Framework (Python)

framework/pages/base_page.py

from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC

class BasePage: def init(self, driver: WebDriver): self.driver = driver self.wait = WebDriverWait(driver, 10)

def goto(self, path: str):
    self.driver.get(f"{self.base_url}{path}")

def wait_for_element(self, locator):
    return self.wait.until(EC.presence_of_element_located(locator))

framework/conftest.py

import pytest from selenium import webdriver from framework.config import config

@pytest.fixture(scope='session') def browser(): """Setup browser for test session.""" driver = webdriver.Chrome() driver.implicitly_wait(10) yield driver driver.quit()

@pytest.fixture def page(browser): """Provide clean page for each test.""" browser.delete_all_cookies() return browser

@pytest.fixture def test_user(db_session): """Create test user.""" from framework.factories import UserFactory user = UserFactory.create() db_session.add(user) db_session.commit() yield user db_session.delete(user) db_session.commit()

tests/test_login.py

from framework.pages.login_page import LoginPage

def test_login_success(page, test_user): """Test successful login.""" login_page = LoginPage(page) login_page.goto() login_page.login(test_user.email, 'password123')

assert login_page.is_logged_in()
  1. Test Organization test-automation/ ├── framework/ │ ├── pages/ │ │ ├── BasePage.ts │ │ ├── LoginPage.ts │ │ ├── ProductPage.ts │ │ └── CheckoutPage.ts │ ├── fixtures/ │ │ ├── database.ts │ │ └── api.ts │ ├── utils/ │ │ ├── helpers.ts │ │ ├── validators.ts │ │ └── waiters.ts │ ├── config/ │ │ └── config.ts │ └── reporters/ │ └── CustomReporter.ts ├── tests/ │ ├── e2e/ │ │ ├── checkout.test.ts │ │ └── search.test.ts │ ├── integration/ │ │ └── api.test.ts │ ├── visual/ │ │ └── components.test.ts │ └── accessibility/ │ └── a11y.test.ts ├── data/ │ ├── fixtures/ │ └── test-data.json ├── playwright.config.ts └── package.json

Framework Patterns Singleton Pattern class TestContext { private static instance: TestContext; private data: Map = new Map();

private constructor() {}

static getInstance(): TestContext { if (!TestContext.instance) { TestContext.instance = new TestContext(); } return TestContext.instance; }

set(key: string, value: any): void { this.data.set(key, value); }

get(key: string): any { return this.data.get(key); } }

Builder Pattern class TestDataBuilder { private data: Partial = {};

withEmail(email: string): this { this.data.email = email; return this; }

withName(name: string): this { this.data.name = name; return this; }

withRole(role: string): this { this.data.role = role; return this; }

build(): User { return { email: this.data.email || 'test@example.com', name: this.data.name || 'Test User', role: this.data.role || 'user', ...this.data, } as User; } }

Best Practices ✅ DO Use Page Object Model for UI tests Create reusable test utilities Implement proper wait strategies Use fixtures for test data Configure for multiple environments Generate readable test reports Organize tests by feature/type Version control test framework ❌ DON'T Put test logic in page objects Use hard-coded waits (sleep) Duplicate test setup code Mix test data with test logic Skip error handling Ignore test flakiness Create overly complex abstractions Hardcode environment URLs Tools & Libraries Playwright: Modern browser automation Selenium: Cross-browser testing Cypress: JavaScript E2E framework pytest: Python testing framework JUnit: Java testing framework TestNG: Advanced Java framework Robot Framework: Keyword-driven testing Examples

See also: e2e-testing-automation, integration-testing, continuous-testing for implementing comprehensive test automation.

返回排行榜