A comprehensive guide for QA automation engineers using Playwright with TypeScript and JavaScript for reliable end-to-end testing across web applications
Cet exemple Agents.md met en avant les meilleures pratiques du secteur pour l’instruction d’agents IA. Il fournit un modèle complet que vous pouvez adapter aux besoins spécifiques de votre projet.
Chaque élément de cet exemple Agents.md a été soigneusement conçu pour optimiser les performances d’OpenAI Codex et garantir un comportement d’agent IA cohérent tout au long de votre flux de développement.
Pour utiliser cet exemple Agents.md dans votre projet, téléchargez le modèle et personnalisez-le selon vos besoins. Cet exemple Agents.md constitue une base solide pour la configuration de votre agent IA.
Étudiez la structure et les conventions utilisées dans cet exemple Agents.md pour comprendre comment les projets réussis mettent en place l’instruction d’agents IA avec des résultats optimaux.
This comprehensive guide outlines best practices for QA automation engineers using Playwright with TypeScript and JavaScript for end-to-end testing. The guide emphasizes writing reliable, maintainable tests that reflect real user behavior, utilizing Playwright's modern testing capabilities including fixtures, web-first assertions, and cross-browser compatibility.
# Initialize new project
npm init playwright@latest
# Or add to existing project
npm install -D @playwright/test
npx playwright install
# Install specific browsers
npx playwright install chromium firefox webkit
# Install system dependencies (Linux)
npx playwright install-deps
e2e-tests/
├── tests/ # Test files
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── registration.spec.ts
│ ├── user-management/
│ │ ├── profile.spec.ts
│ │ └── settings.spec.ts
│ └── api/
│ └── user-api.spec.ts
├── fixtures/ # Custom fixtures
│ ├── auth-fixture.ts
│ └── database-fixture.ts
├── page-objects/ # Page Object Models
│ ├── login-page.ts
│ ├── dashboard-page.ts
│ └── base-page.ts
├── utils/ # Helper utilities
│ ├── test-data.ts
│ ├── api-helpers.ts
│ └── database-helpers.ts
├── config/ # Configuration files
│ ├── environments.ts
│ └── test-data.json
├── reports/ # Test reports
├── screenshots/ # Visual comparisons
├── playwright.config.ts # Main configuration
├── global-setup.ts # Global setup
├── global-teardown.ts # Global teardown
└── package.json
// tests/user-management/profile.spec.ts
import { test, expect } from "@playwright/test";
import { LoginPage } from "../page-objects/login-page";
import { ProfilePage } from "../page-objects/profile-page";
test.describe("User Profile Management", () => {
test.beforeEach(async ({ page }) => {
// Setup: Navigate to application and login
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginWithValidCredentials(
"user@example.com",
"password123"
);
});
test("should display user profile information correctly", async ({
page,
}) => {
const profilePage = new ProfilePage(page);
await profilePage.goto();
// Verify profile information is displayed
await expect(profilePage.userNameField).toBeVisible();
await expect(profilePage.emailField).toHaveValue("user@example.com");
await expect(profilePage.profileImage).toBeVisible();
});
test("should update user profile successfully", async ({ page }) => {
const profilePage = new ProfilePage(page);
await profilePage.goto();
const newName = "Updated User Name";
await profilePage.updateUserName(newName);
await profilePage.saveChanges();
// Verify success message and updated information
await expect(profilePage.successMessage).toBeVisible();
await expect(profilePage.successMessage).toHaveText(
"Profile updated successfully"
);
await expect(profilePage.userNameField).toHaveValue(newName);
});
test("should validate required fields when updating profile", async ({
page,
}) => {
const profilePage = new ProfilePage(page);
await profilePage.goto();
// Clear required field and attempt to save
await profilePage.clearUserName();
await profilePage.saveChanges();
// Verify validation error
await expect(profilePage.nameValidationError).toBeVisible();
await expect(profilePage.nameValidationError).toHaveText(
"Name is required"
);
});
});
// page-objects/base-page.ts
import { Page, Locator } from "@playwright/test";
export abstract class BasePage {
protected page: Page;
constructor(page: Page) {
this.page = page;
}
/**
* Wait for the page to be fully loaded
*/
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
}
/**
* Take a screenshot of the current page
*/
async takeScreenshot(name: string): Promise<void> {
await this.page.screenshot({
path: `screenshots/${name}.png`,
fullPage: true,
});
}
}
// page-objects/login-page.ts
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "./base-page";
export class LoginPage extends BasePage {
// Use role-based and semantic locators
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
readonly forgotPasswordLink: Locator;
constructor(page: Page) {
super(page);
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
this.loginButton = page.getByRole("button", { name: "Sign In" });
this.errorMessage = page.getByTestId("login-error");
this.forgotPasswordLink = page.getByRole("link", {
name: "Forgot Password?",
});
}
async goto(): Promise<void> {
await this.page.goto("/login");
await this.waitForPageLoad();
}
/**
* Login with provided credentials
*/
async loginWithCredentials(email: string, password: string): Promise<void> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
/**
* Login with valid test credentials
*/
async loginWithValidCredentials(
email?: string,
password?: string
): Promise<void> {
const testEmail =
email || process.env.TEST_USER_EMAIL || "test@example.com";
const testPassword =
password || process.env.TEST_USER_PASSWORD || "password123";
await this.loginWithCredentials(testEmail, testPassword);
await this.page.waitForURL("/dashboard");
}
/**
* Verify login error is displayed
*/
async verifyLoginError(expectedMessage: string): Promise<void> {
await expect(this.errorMessage).toBeVisible();
await expect(this.errorMessage).toHaveText(expectedMessage);
}
}
// page-objects/profile-page.ts
import { Page, Locator } from "@playwright/test";
import { BasePage } from "./base-page";
export class ProfilePage extends BasePage {
readonly userNameField: Locator;
readonly emailField: Locator;
readonly profileImage: Locator;
readonly saveButton: Locator;
readonly successMessage: Locator;
readonly nameValidationError: Locator;
constructor(page: Page) {
super(page);
this.userNameField = page.getByLabel("Full Name");
this.emailField = page.getByLabel("Email Address");
this.profileImage = page.getByRole("img", { name: "Profile Picture" });
this.saveButton = page.getByRole("button", { name: "Save Changes" });
this.successMessage = page.getByTestId("success-message");
this.nameValidationError = page.getByTestId("name-validation-error");
}
async goto(): Promise<void> {
await this.page.goto("/profile");
await this.waitForPageLoad();
}
/**
* Update user name field
*/
async updateUserName(name: string): Promise<void> {
await this.userNameField.clear();
await this.userNameField.fill(name);
}
/**
* Clear user name field
*/
async clearUserName(): Promise<void> {
await this.userNameField.clear();
}
/**
* Save profile changes
*/
async saveChanges(): Promise<void> {
await this.saveButton.click();
}
}
// 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"],
["junit", { outputFile: "reports/junit-results.xml" }],
["json", { outputFile: "reports/test-results.json" }],
],
use: {
baseURL: process.env.BASE_URL || "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"] },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 12"] },
},
{
name: "Microsoft Edge",
use: { ...devices["Desktop Edge"], channel: "msedge" },
},
],
webServer: {
command: "npm run start",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
// fixtures/auth-fixture.ts
import { test as base, expect } from "@playwright/test";
import { LoginPage } from "../page-objects/login-page";
type AuthFixtures = {
authenticatedPage: any;
loginPage: LoginPage;
};
export const test = base.extend<AuthFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},
authenticatedPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginWithValidCredentials();
await use(page);
},
});
export { expect };
// config/environments.ts
export interface Environment {
baseUrl: string;
apiUrl: string;
testUser: {
email: string;
password: string;
};
adminUser: {
email: string;
password: string;
};
}
export const environments: Record<string, Environment> = {
development: {
baseUrl: "http://localhost:3000",
apiUrl: "http://localhost:3001/api",
testUser: {
email: "test@example.com",
password: "password123",
},
adminUser: {
email: "admin@example.com",
password: "admin123",
},
},
staging: {
baseUrl: "https://staging.example.com",
apiUrl: "https://api-staging.example.com",
testUser: {
email: process.env.STAGING_TEST_EMAIL!,
password: process.env.STAGING_TEST_PASSWORD!,
},
adminUser: {
email: process.env.STAGING_ADMIN_EMAIL!,
password: process.env.STAGING_ADMIN_PASSWORD!,
},
},
production: {
baseUrl: "https://example.com",
apiUrl: "https://api.example.com",
testUser: {
email: process.env.PROD_TEST_EMAIL!,
password: process.env.PROD_TEST_PASSWORD!,
},
adminUser: {
email: process.env.PROD_ADMIN_EMAIL!,
password: process.env.PROD_ADMIN_PASSWORD!,
},
},
};
export function getEnvironment(): Environment {
const env = process.env.NODE_ENV || "development";
return environments[env];
}
// tests/api/user-api.spec.ts
import { test, expect } from "@playwright/test";
test.describe("User API Tests", () => {
let apiContext: any;
test.beforeAll(async ({ playwright }) => {
apiContext = await playwright.request.newContext({
baseURL: "https://api.example.com",
extraHTTPHeaders: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
"Content-Type": "application/json",
},
});
});
test.afterAll(async () => {
await apiContext.dispose();
});
test("should create user via API", async () => {
const userData = {
name: "Test User",
email: "testuser@example.com",
password: "securePassword123",
};
const response = await apiContext.post("/users", {
data: userData,
});
expect(response.status()).toBe(201);
const responseBody = await response.json();
expect(responseBody).toHaveProperty("id");
expect(responseBody.email).toBe(userData.email);
expect(responseBody.name).toBe(userData.name);
});
test("should retrieve user by ID", async () => {
// First create a user
const createResponse = await apiContext.post("/users", {
data: {
name: "Retrieve Test User",
email: "retrieve@example.com",
password: "password123",
},
});
const createdUser = await createResponse.json();
// Then retrieve the user
const getResponse = await apiContext.get(`/users/${createdUser.id}`);
expect(getResponse.status()).toBe(200);
const retrievedUser = await getResponse.json();
expect(retrievedUser.id).toBe(createdUser.id);
expect(retrievedUser.email).toBe("retrieve@example.com");
});
});
// tests/visual/homepage.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Visual Regression Tests", () => {
test("homepage should match visual baseline", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
// Hide dynamic content that changes between runs
await page.addStyleTag({
content: `
.timestamp, .live-chat, .ads {
visibility: hidden !important;
}
`,
});
await expect(page).toHaveScreenshot("homepage.png");
});
test("user profile page should match visual baseline", async ({ page }) => {
// Login first
await page.goto("/login");
await page.getByLabel("Email").fill("test@example.com");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Sign In" }).click();
// Navigate to profile
await page.goto("/profile");
await page.waitForLoadState("networkidle");
// Mask dynamic content
await expect(page).toHaveScreenshot("profile-page.png", {
mask: [page.getByTestId("last-login-time")],
});
});
});
// utils/test-helpers.ts
/**
* Generate random test data for user creation
*/
export function generateTestUser() {
const timestamp = Date.now();
return {
name: `Test User ${timestamp}`,
email: `test${timestamp}@example.com`,
password: "TestPassword123!",
};
}
/**
* Wait for element to be stable (not moving/changing)
*/
export async function waitForElementStable(locator: any, timeout = 5000) {
let previousBoundingBox = await locator.boundingBox();
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
await new Promise((resolve) => setTimeout(resolve, 100));
const currentBoundingBox = await locator.boundingBox();
if (
JSON.stringify(previousBoundingBox) === JSON.stringify(currentBoundingBox)
) {
return;
}
previousBoundingBox = currentBoundingBox;
}
throw new Error(`Element did not stabilize within ${timeout}ms`);
}
/**
* Fill form with retry mechanism
*/
export async function fillFormWithRetry(
locator: any,
value: string,
maxRetries = 3
) {
for (let i = 0; i < maxRetries; i++) {
try {
await locator.clear();
await locator.fill(value);
// Verify the value was set correctly
const actualValue = await locator.inputValue();
if (actualValue === value) {
return;
}
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
/**
* Handle file upload with validation
*/
export async function uploadFile(
page: any,
fileInputSelector: string,
filePath: string
) {
const fileInput = page.locator(fileInputSelector);
await fileInput.setInputFiles(filePath);
// Wait for upload to complete
await page.waitForFunction((selector) => {
const input = document.querySelector(selector);
return input && input.files && input.files.length > 0;
}, fileInputSelector);
}
This comprehensive guide provides a solid foundation for building reliable, maintainable end-to-end tests using Playwright with TypeScript and JavaScript, ensuring high-quality QA automation that reflects real user behavior and maintains stability across different browsers and devices.