Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/annotations.md
1# Test Annotations & Organization23## Table of Contents451. [Skip Annotations](#skip-annotations)62. [Fixme & Fail Annotations](#fixme--fail-annotations)73. [Slow Tests](#slow-tests)84. [Test Steps](#test-steps)95. [Custom Annotations](#custom-annotations)106. [Conditional Annotations](#conditional-annotations)1112## Skip Annotations1314### Basic Skip1516```typescript17// Skip unconditionally18test.skip("feature not implemented", async ({ page }) => {19// This test won't run20});2122// Skip with reason23test("payment flow", async ({ page }) => {24test.skip(true, "Payment gateway in maintenance");25// Test body won't execute26});27```2829### Conditional Skip3031```typescript32test("webkit-specific feature", async ({ page, browserName }) => {33test.skip(browserName !== "webkit", "This feature only works in WebKit");3435await page.goto("/webkit-feature");36});3738test("production only", async ({ page }) => {39test.skip(process.env.ENV !== "production", "Only runs against production");4041await page.goto("/prod-feature");42});43```4445### Skip by Platform4647```typescript48test("windows-specific", async ({ page }) => {49test.skip(process.platform !== "win32", "Windows only");50});5152test("not on CI", async ({ page }) => {53test.skip(!!process.env.CI, "Skipped in CI environment");54});55```5657### Skip Describe Block5859```typescript60test.describe("Admin features", () => {61test.skip(62({ browserName }) => browserName === "firefox",63"Firefox admin bug",64);6566test("admin dashboard", async ({ page }) => {67// Skipped in Firefox68});6970test("admin settings", async ({ page }) => {71// Skipped in Firefox72});73});74```7576## Fixme & Fail Annotations7778### Fixme - Known Issues7980```typescript81// Mark test as needing fix (skips the test)82test.fixme("broken after refactor", async ({ page }) => {83// Test won't run but is tracked84});8586// Conditional fixme87test("flaky on CI", async ({ page }) => {88test.fixme(!!process.env.CI, "Investigate CI flakiness - ticket #123");8990await page.goto("/flaky-feature");91});92```9394### Fail - Expected Failures9596```typescript97// Test is expected to fail (runs but expects failure)98test("known bug", async ({ page }) => {99test.fail();100101await page.goto("/buggy-page");102// If this passes, the test fails (bug was fixed!)103await expect(page.getByText("Working")).toBeVisible();104});105106// Conditional fail107test("fails on webkit", async ({ page, browserName }) => {108test.fail(browserName === "webkit", "WebKit rendering bug #456");109110await page.goto("/render-test");111await expect(page.getByTestId("element")).toHaveCSS("width", "100px");112});113```114115### Difference Between Skip, Fixme, Fail116117| Annotation | Runs? | Use Case |118| -------------- | ----- | -------------------------------- |119| `test.skip()` | No | Feature not applicable |120| `test.fixme()` | No | Known bug, needs investigation |121| `test.fail()` | Yes | Expected to fail, tracking a bug |122123## Slow Tests124125### Mark Slow Tests126127```typescript128// Triple the default timeout129test("large data import", async ({ page }) => {130test.slow();131132await page.goto("/import");133await page.setInputFiles("#file", "large-file.csv");134await page.getByRole("button", { name: "Import" }).click();135136await expect(page.getByText("Import complete")).toBeVisible();137});138139// Conditional slow140test("video processing", async ({ page, browserName }) => {141test.slow(browserName === "webkit", "WebKit video processing is slow");142143await page.goto("/video-editor");144});145```146147### Custom Timeout148149```typescript150test("very long operation", async ({ page }) => {151// Set specific timeout (in milliseconds)152test.setTimeout(120000); // 2 minutes153154await page.goto("/long-operation");155});156157// Timeout for describe block158test.describe("Integration tests", () => {159test.describe.configure({ timeout: 60000 });160161test("test 1", async ({ page }) => {162// Has 60 second timeout163});164});165```166167## Test Steps168169### Basic Steps170171```typescript172test("checkout flow", async ({ page }) => {173await test.step("Add item to cart", async () => {174await page.goto("/products");175await page.getByRole("button", { name: "Add to Cart" }).click();176});177178await test.step("Go to checkout", async () => {179await page.getByRole("link", { name: "Cart" }).click();180await page.getByRole("button", { name: "Checkout" }).click();181});182183await test.step("Fill shipping info", async () => {184await page.getByLabel("Address").fill("123 Test St");185await page.getByLabel("City").fill("Test City");186});187188await test.step("Complete payment", async () => {189await page.getByLabel("Card").fill("4242424242424242");190await page.getByRole("button", { name: "Pay" }).click();191});192193await expect(page.getByText("Order confirmed")).toBeVisible();194});195```196197### Nested Steps198199```typescript200test("user registration", async ({ page }) => {201await test.step("Fill registration form", async () => {202await page.goto("/register");203204await test.step("Personal info", async () => {205await page.getByLabel("Name").fill("John Doe");206await page.getByLabel("Email").fill("[email protected]");207});208209await test.step("Security", async () => {210await page.getByLabel("Password").fill("SecurePass123");211await page.getByLabel("Confirm Password").fill("SecurePass123");212});213});214215await test.step("Submit and verify", async () => {216await page.getByRole("button", { name: "Register" }).click();217await expect(page.getByText("Welcome")).toBeVisible();218});219});220```221222### Steps with Return Values223224```typescript225test("verify order", async ({ page }) => {226const orderId = await test.step("Create order", async () => {227await page.goto("/checkout");228await page.getByRole("button", { name: "Place Order" }).click();229230// Return value from step231return await page.getByTestId("order-id").textContent();232});233234await test.step("Verify order details", async () => {235await page.goto(`/orders/${orderId}`);236await expect(page.getByText(`Order #${orderId}`)).toBeVisible();237});238});239```240241### Step in Page Object242243```typescript244// pages/checkout.page.ts245export class CheckoutPage {246async fillShippingInfo(address: string, city: string) {247await test.step("Fill shipping information", async () => {248await this.page.getByLabel("Address").fill(address);249await this.page.getByLabel("City").fill(city);250});251}252253async completePayment(cardNumber: string) {254await test.step("Complete payment", async () => {255await this.page.getByLabel("Card").fill(cardNumber);256await this.page.getByRole("button", { name: "Pay" }).click();257});258}259}260```261262## Custom Annotations263264### Add Annotations265266```typescript267test("important feature", async ({ page }, testInfo) => {268// Add custom annotation269testInfo.annotations.push({270type: "priority",271description: "high",272});273274testInfo.annotations.push({275type: "ticket",276description: "JIRA-123",277});278279await page.goto("/feature");280});281```282283### Annotation Fixture284285```typescript286// fixtures/annotations.fixture.ts287import { test as base, TestInfo } from "@playwright/test";288289type AnnotationFixtures = {290annotate: {291ticket: (id: string) => void;292priority: (level: "low" | "medium" | "high") => void;293owner: (name: string) => void;294};295};296297export const test = base.extend<AnnotationFixtures>({298annotate: async ({}, use, testInfo) => {299await use({300ticket: (id) => {301testInfo.annotations.push({ type: "ticket", description: id });302},303priority: (level) => {304testInfo.annotations.push({ type: "priority", description: level });305},306owner: (name) => {307testInfo.annotations.push({ type: "owner", description: name });308},309});310},311});312313// Usage314test("critical feature", async ({ page, annotate }) => {315annotate.ticket("JIRA-456");316annotate.priority("high");317annotate.owner("Alice");318319await page.goto("/critical");320});321```322323### Read Annotations in Reporter324325```typescript326// reporters/annotation-reporter.ts327import { Reporter, TestCase, TestResult } from "@playwright/test/reporter";328329class AnnotationReporter implements Reporter {330onTestEnd(test: TestCase, result: TestResult) {331const ticket = test.annotations.find((a) => a.type === "ticket");332const priority = test.annotations.find((a) => a.type === "priority");333334if (ticket) {335console.log(`Test linked to: ${ticket.description}`);336}337338if (priority?.description === "high" && result.status === "failed") {339console.log(`HIGH PRIORITY FAILURE: ${test.title}`);340}341}342}343344export default AnnotationReporter;345```346347## Conditional Annotations348349### Annotation Helper350351```typescript352// helpers/test-annotations.ts353import { test } from "@playwright/test";354355export function skipInCI(reason = "Skipped in CI") {356test.skip(!!process.env.CI, reason);357}358359export function skipInBrowser(browser: string, reason: string) {360test.beforeEach(({ browserName }) => {361test.skip(browserName === browser, reason);362});363}364365export function onlyInEnv(env: string) {366test.skip(process.env.ENV !== env, `Only runs in ${env}`);367}368```369370```typescript371// tests/feature.spec.ts372import { skipInCI, onlyInEnv } from "../helpers/test-annotations";373374test("local only feature", async ({ page }) => {375skipInCI("Uses local resources");376377await page.goto("/local-feature");378});379380test("production check", async ({ page }) => {381onlyInEnv("production");382383await page.goto("/prod-only");384});385```386387### Describe-Level Conditions388389```typescript390test.describe("Mobile features", () => {391test.beforeEach(({ isMobile }) => {392test.skip(!isMobile, "Mobile only tests");393});394395test("touch gestures", async ({ page }) => {396// Only runs on mobile397});398});399400test.describe("Desktop features", () => {401test.beforeEach(({ isMobile }) => {402test.skip(isMobile, "Desktop only tests");403});404405test("hover interactions", async ({ page }) => {406// Only runs on desktop407});408});409```410411## Anti-Patterns to Avoid412413| Anti-Pattern | Problem | Solution |414| --------------------------- | ---------------------- | -------------------------------- |415| Skipping without reason | Hard to track why | Always provide description |416| Too many skipped tests | Test debt accumulates | Review and clean up regularly |417| Using skip instead of fixme | Loses intent | Use fixme for bugs, skip for N/A |418| Not using steps | Hard to debug failures | Group logical actions in steps |419420## Related References421422- **Test Tags**: See [test-tags.md](test-tags.md) for tagging and filtering tests with `--grep`423- **Test Organization**: See [test-suite-structure.md](test-suite-structure.md) for structuring tests424- **Debugging**: See [debugging.md](../debugging/debugging.md) for troubleshooting425