Write reliable end-to-end tests with Playwright for cross-browser testing and automation.
# Playwright E2E Testing for Google Antigravity
Master end-to-end testing with Playwright in your Google Antigravity projects. This guide covers test architecture, page objects, visual testing, and CI/CD integration for reliable browser automation.
## Playwright Configuration
Set up a comprehensive test environment:
```typescript
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
["html", { open: "never" }],
["json", { outputFile: "test-results/results.json" }],
["junit", { outputFile: "test-results/junit.xml" }],
],
use: {
baseURL: process.env.BASE_URL || "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
actionTimeout: 15000,
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"] },
},
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});
```
## Page Object Model
Create maintainable page objects:
```typescript
// e2e/pages/BasePage.ts
import { Page, Locator, expect } from "@playwright/test";
export abstract class BasePage {
readonly page: Page;
readonly url: string;
constructor(page: Page, url: string) {
this.page = page;
this.url = url;
}
async goto(): Promise<void> {
await this.page.goto(this.url);
await this.waitForPageLoad();
}
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
}
async getTitle(): Promise<string> {
return this.page.title();
}
async screenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `screenshots/${name}.png` });
}
}
// e2e/pages/LoginPage.ts
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "./BasePage";
export class LoginPage extends BasePage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
readonly forgotPasswordLink: Locator;
constructor(page: Page) {
super(page, "/login");
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
this.submitButton = page.getByRole("button", { name: "Sign In" });
this.errorMessage = page.getByRole("alert");
this.forgotPasswordLink = page.getByRole("link", { name: "Forgot password?" });
}
async login(email: string, password: string): Promise<void> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string): Promise<void> {
await expect(this.errorMessage).toContainText(message);
}
async expectSuccessfulLogin(): Promise<void> {
await expect(this.page).toHaveURL("/dashboard");
}
}
// e2e/pages/DashboardPage.ts
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "./BasePage";
export class DashboardPage extends BasePage {
readonly welcomeMessage: Locator;
readonly userMenu: Locator;
readonly logoutButton: Locator;
readonly statsCards: Locator;
readonly recentActivity: Locator;
constructor(page: Page) {
super(page, "/dashboard");
this.welcomeMessage = page.getByTestId("welcome-message");
this.userMenu = page.getByTestId("user-menu");
this.logoutButton = page.getByRole("button", { name: "Logout" });
this.statsCards = page.getByTestId("stats-card");
this.recentActivity = page.getByTestId("recent-activity");
}
async logout(): Promise<void> {
await this.userMenu.click();
await this.logoutButton.click();
await expect(this.page).toHaveURL("/login");
}
async getStatsCount(): Promise<number> {
return this.statsCards.count();
}
}
```
## Test Fixtures and Utilities
Create reusable test utilities:
```typescript
// e2e/fixtures/auth.fixture.ts
import { test as base, Page } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
import { DashboardPage } from "../pages/DashboardPage";
type AuthFixtures = {
loginPage: LoginPage;
dashboardPage: DashboardPage;
authenticatedPage: Page;
};
export const test = base.extend<AuthFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},
dashboardPage: async ({ page }, use) => {
const dashboardPage = new DashboardPage(page);
await use(dashboardPage);
},
authenticatedPage: async ({ page, context }, use) => {
// Set up authentication state
await context.addCookies([
{
name: "auth_token",
value: process.env.TEST_AUTH_TOKEN!,
domain: "localhost",
path: "/",
},
]);
await page.goto("/dashboard");
await use(page);
},
});
export { expect } from "@playwright/test";
```
## Writing Comprehensive Tests
Create thorough test suites:
```typescript
// e2e/tests/auth.spec.ts
import { test, expect } from "../fixtures/auth.fixture";
test.describe("Authentication Flow", () => {
test.beforeEach(async ({ loginPage }) => {
await loginPage.goto();
});
test("should display login form", async ({ loginPage }) => {
await expect(loginPage.emailInput).toBeVisible();
await expect(loginPage.passwordInput).toBeVisible();
await expect(loginPage.submitButton).toBeVisible();
});
test("should show error for invalid credentials", async ({ loginPage }) => {
await loginPage.login("invalid@example.com", "wrongpassword");
await loginPage.expectError("Invalid email or password");
});
test("should login successfully with valid credentials", async ({ loginPage }) => {
await loginPage.login("test@example.com", "password123");
await loginPage.expectSuccessfulLogin();
});
test("should validate required fields", async ({ loginPage }) => {
await loginPage.submitButton.click();
await expect(loginPage.page.getByText("Email is required")).toBeVisible();
await expect(loginPage.page.getByText("Password is required")).toBeVisible();
});
test("should navigate to forgot password", async ({ loginPage }) => {
await loginPage.forgotPasswordLink.click();
await expect(loginPage.page).toHaveURL("/forgot-password");
});
});
test.describe("Dashboard", () => {
test("should display user dashboard after login", async ({ authenticatedPage }) => {
const dashboardPage = new DashboardPage(authenticatedPage);
await expect(dashboardPage.welcomeMessage).toBeVisible();
await expect(dashboardPage.statsCards.first()).toBeVisible();
});
test("should logout successfully", async ({ authenticatedPage }) => {
const dashboardPage = new DashboardPage(authenticatedPage);
await dashboardPage.logout();
await expect(authenticatedPage).toHaveURL("/login");
});
});
```
## Visual Regression Testing
Implement screenshot comparisons:
```typescript
// e2e/tests/visual.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Visual Regression", () => {
test("homepage matches snapshot", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
await expect(page).toHaveScreenshot("homepage.png", {
fullPage: true,
maxDiffPixelRatio: 0.01,
});
});
test("login page matches snapshot", async ({ page }) => {
await page.goto("/login");
await expect(page).toHaveScreenshot("login-page.png", {
mask: [page.locator("[data-testid='dynamic-content']")],
});
});
test("responsive design - mobile", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto("/");
await expect(page).toHaveScreenshot("homepage-mobile.png");
});
});
```
## API Mocking
Mock API responses for isolated testing:
```typescript
// e2e/tests/api-mock.spec.ts
import { test, expect } from "@playwright/test";
test.describe("API Mocking", () => {
test("should handle API errors gracefully", async ({ page }) => {
await page.route("**/api/users", (route) => {
route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "Internal server error" }),
});
});
await page.goto("/users");
await expect(page.getByText("Something went wrong")).toBeVisible();
});
test("should display mocked data", async ({ page }) => {
await page.route("**/api/products", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
products: [
{ id: 1, name: "Test Product", price: 99.99 },
],
}),
});
});
await page.goto("/products");
await expect(page.getByText("Test Product")).toBeVisible();
});
});
```
Google Antigravity generates comprehensive E2E test suites with Playwright that ensure application reliability through page objects, fixtures, and visual testing.This Playwright prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their playwright implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Playwright projects, consider mentioning your framework version, coding style, and any specific libraries you're using.