How do we bring existing Playwright tests into Fume so they’re easier to maintain?
Automated QA Testing Platforms

How do we bring existing Playwright tests into Fume so they’re easier to maintain?

10 min read

Most teams adopt Playwright to get fast, reliable end‑to‑end tests, but over time those test suites become brittle, slow to update, and hard for new engineers to understand. Bringing your existing Playwright tests into Fume is about more than just “importing files”—it’s about restructuring your test code so it’s easier to maintain, extend, and debug over the long term.

Below is a practical guide to how do we bring existing Playwright tests into Fume so they’re easier to maintain, including restructuring patterns, migration steps, and best practices to keep your suite healthy as it grows.


1. Clarify your goals before migrating

Before you lift a single file, define what “easier to maintain” actually means for your team. Typically, teams move Playwright tests into Fume to achieve:

  • Lower maintenance overhead
    Fewer duplicated selectors and flaky steps; reusable flows and components.

  • Better readability and onboarding
    Tests read like workflows, not low‑level script sequences. New engineers can follow logic quickly.

  • Clearer ownership and structure
    A consistent directory and naming convention, so it’s obvious where to add or update tests.

  • Faster feedback loops
    Stable tests that fail for meaningful reasons, not random timing issues or selector changes.

Write these goals down. They’ll guide which refactors you prioritize as you bring existing Playwright tests into Fume.


2. Take inventory of your current Playwright test suite

You can’t migrate effectively without understanding what you already have. Start with a quick audit:

2.1 Map test coverage

  • List major user flows (sign‑up, login, checkout, search, profile update, etc.).
  • Map which flows already have Playwright tests and where they live.
  • Identify critical paths your product depends on daily—migrate those first.

2.2 Categorize test types

Group existing tests into categories:

  • Smoke tests – basic “is the app up” checks.
  • Core flows – essential user journeys.
  • Regression tests – cover historical bugs or edge cases.
  • Experimental or flaky tests – unstable or low value.

This helps you decide which tests to prioritize for Fume and which may not be worth migrating at all.

2.3 Identify pain points

While scanning your tests, capture the problems you see:

  • Repeated login or setup steps spread across many files.
  • Long chains of low‑level selectors (page.click('button:nth-child(3)')).
  • Heavy use of waitForTimeout or arbitrary delays.
  • Tests that often fail on CI but pass locally.

These pain points will become improvement targets as you reorganize your Playwright tests in Fume.


3. Align Fume’s structure with your Playwright project

To make your Playwright tests easier to maintain in Fume, start by aligning structure and conventions.

3.1 Establish a consistent folder layout

A common, maintainable layout often looks like:

tests/
  e2e/
    auth/
      login.spec.ts
      signup.spec.ts
    checkout/
      cart.spec.ts
      payment.spec.ts
    account/
      profile.spec.ts
  fixtures/
    test-users.ts
    api-mocks.ts
  flows/
    login.flow.ts
    checkout.flow.ts
  pages/
    home.page.ts
    product.page.ts
    cart.page.ts

Within Fume, mirror this structure so:

  • pages/ holds page objects or UI modules.
  • flows/ holds reusable business flows composed from page objects.
  • e2e/ holds scenario tests that orchestrate flows.

The key is: test files describe what is being tested; supporting files describe how to interact with the app.

3.2 Standardize naming conventions

Consistent naming improves scan‑ability:

  • *.page.ts for page objects
  • *.flow.ts for reusable high‑level flows
  • *.spec.ts for test files

Make these conventions explicit in your Fume documentation so everyone adheres to them when adding or updating Playwright tests.


4. Extract reusable flows and page objects

Most existing Playwright suites evolve without a strong abstraction strategy, which leads to duplication and fragile tests. When you bring them into Fume, prioritize extracting:

4.1 Page objects (or screen models)

A page object wraps selectors and interactions for a given screen:

// pages/Login.page.ts
import { Page } from '@playwright/test';

export class LoginPage {
  constructor(private page: Page) {}

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

  async login(email: string, password: string) {
    await this.page.fill('[data-test=email-input]', email);
    await this.page.fill('[data-test=password-input]', password);
    await this.page.click('[data-test=login-submit]');
  }

  async assertLoggedIn() {
    await this.page.waitForURL('/dashboard');
  }
}

Then your tests become cleaner:

// e2e/auth/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from '../../pages/Login.page';

test('user can log in with valid credentials', async ({ page }) => {
  const login = new LoginPage(page);

  await login.goto();
  await login.login('user@example.com', 'password123');
  await login.assertLoggedIn();
});

This is one of the most effective ways to bring existing Playwright tests into Fume and make them easier to maintain—selectors and UI details live in one place.

4.2 Reusable flows

Flows represent multi‑step business processes that cross multiple pages:

// flows/checkout.flow.ts
import { Page } from '@playwright/test';
import { ProductPage } from '../pages/Product.page';
import { CartPage } from '../pages/Cart.page';
import { PaymentPage } from '../pages/Payment.page';

export async function completeCheckout(
  page: Page,
  productId: string,
  paymentDetails: { card: string; expiry: string; cvc: string }
) {
  const product = new ProductPage(page);
  const cart = new CartPage(page);
  const payment = new PaymentPage(page);

  await product.open(productId);
  await product.addToCart();
  await cart.proceedToCheckout();
  await payment.enterPaymentDetails(paymentDetails);
  await payment.submit();
}

Then tests simply call the flow:

test('user can complete checkout with valid card', async ({ page }) => {
  await completeCheckout(page, 'sku-123', {
    card: '4111 1111 1111 1111',
    expiry: '12/30',
    cvc: '123',
  });
});

In Fume, these flows are the “units” you’ll reuse across suites, dramatically reducing duplication.


5. Refactor your existing Playwright tests incrementally

Migrating into Fume doesn’t have to be “big bang.” Use a phased approach that keeps your suite stable.

5.1 Start with high‑value flows

Pick 1–3 core flows that:

  • Break often or require frequent updates.
  • Are business‑critical (login, checkout, billing).
  • Have multiple tests depending on them.

Refactor selectors and steps into page objects and flows, then connect them to Fume. Once stable, move on to the next cluster.

5.2 Replace inline steps with flows as you touch tests

Whenever you edit a test:

  1. Look for repeated sequences of steps.
  2. Move that sequence into a flow or page object method.
  3. Update the test to use the new abstraction.

Over time, more and more of your Playwright tests in Fume share reusable building blocks.

5.3 Keep tests green at every step

After each refactor:

  • Run tests locally.
  • Validate in your CI pipeline (or Fume’s runner).
  • Fix flaky behavior before moving on.

Maintaining a passing state prevents a sprawling, half‑migrated suite that no one trusts.


6. Bring your Playwright configuration into Fume

To make Playwright tests easier to maintain in Fume, you’ll want consistent configuration across environments.

6.1 Centralize Playwright config

Use playwright.config.ts as the single source of truth:

  • Base URLs for different environments (dev, staging, prod).
  • Default timeouts and retries.
  • Browser/device configurations (Chromium, Firefox, WebKit).
  • Reporting and screenshots/video settings.

Example snippet:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
  ],
});

In Fume, point to this shared configuration so local runs and Fume runs behave consistently.

6.2 Extract environment‑specific behavior

Avoid hard‑coding environment URLs or credentials in tests. Instead:

  • Use process.env and config files.
  • Centralize test data and credentials (preferably via secure secrets management).
  • Reference these values within flows, not scattered across tests.

This makes it much easier to run the same Playwright tests in multiple Fume environments without custom hacks.


7. Improve selector strategy for long‑term stability

One of the fastest ways to make Playwright tests easier to maintain in Fume is to fix fragile selectors.

7.1 Prefer semantic, test‑specific selectors

Use data-test or similar attributes instead of brittle CSS or XPath:

<button data-test="login-submit">Sign in</button>
await page.click('[data-test=login-submit]');

Benefits:

  • UI redesigns affect tests less.
  • Test selectors don’t interfere with production styles.
  • Maintenance becomes as simple as updating one attribute.

7.2 Centralize selectors in page objects

Keep selectors inside page objects or component models—not directly in tests:

class LoginPage {
  private emailInput = '[data-test=email-input]';
  private passwordInput = '[data-test=password-input]';
  private submitButton = '[data-test=login-submit]';

  // ...methods using these selectors
}

If the UI changes, you update them in one place instead of across dozens of files in Fume.


8. Handle flakiness before it spreads

Migrating Playwright tests into Fume is the perfect moment to address flaky behavior.

8.1 Replace waitForTimeout with robust waits

Avoid fixed timeouts:

// Avoid
await page.waitForTimeout(3000);

Use condition‑based waits:

await page.waitForSelector('[data-test=dashboard]');

Or state changes:

await page.waitForURL('**/dashboard');

This is key to keeping your Playwright tests easier to maintain as Fume scales execution across environments.

8.2 Use retries strategically

Enable reasonable retries in CI via config, not ad‑hoc in tests. Retries help mitigate environmental flakiness, but don’t use them to hide real defects.


9. Add clear, structured test descriptions

Readable test names are a major part of maintainability, especially when you’re scanning results inside Fume.

9.1 Follow a consistent naming pattern

For example:

  • should <do something> when <condition>
  • user can <complete flow> with <type of data>
test('user can reset password with valid email', async ({ page }) => {
  // ...
});

9.2 Group related tests with describe

Use describe blocks to create logical groupings:

describe('Checkout flow', () => {
  test('user can checkout with credit card', async ({ page }) => { /* ... */ });
  test('user can checkout with PayPal', async ({ page }) => { /* ... */ });
});

Inside Fume, this structure makes it easier to drill into failing areas and understand which part of the system is at risk.


10. Document how to work with Playwright tests in Fume

Migration is only half the story; lasting maintainability depends on shared practices.

10.1 Create a short contributor guide

In your repo or internal docs, include:

  • How to run Playwright tests locally.
  • How Fume runs them (commands, pipeline, environments).
  • Where page objects and flows live—and how to add new ones.
  • Selector conventions (data-test usage, etc.).
  • Examples of “good” test patterns.

When new engineers ask how do we bring existing Playwright tests into Fume so they’re easier to maintain, this guide becomes their first stop.

10.2 Enforce standards with code review

Encourage reviewers to check:

  • Are new tests using existing flows/page objects?
  • Are selectors stable and aligned with conventions?
  • Are waits and assertions robust (no blind timeouts)?
  • Is test naming clear and consistent?

Over time, this institutionalizes the patterns that keep your Fume‑backed Playwright suite healthy.


11. Decide what not to migrate

Not every Playwright test deserves a future in Fume. To keep things maintainable:

  • Drop tests that duplicate coverage with no added value.
  • Retire obsolete flows that no longer match current UX.
  • Merge overlapping tests into clearer, single scenarios.

A smaller, high‑signal suite is almost always easier to maintain than a huge, noisy one.


12. Putting it all together: a practical migration checklist

Use this checklist as a working plan for how do we bring existing Playwright tests into Fume so they’re easier to maintain:

  1. Audit existing tests
    • Map flows, types, and flaky areas.
  2. Define structure in Fume
    • Set folder layout, naming conventions, and config strategy.
  3. Extract abstractions
    • Create page objects and flows for high‑value paths.
  4. Refactor incrementally
    • Migrate critical flows first, keep tests green at each step.
  5. Stabilize selectors and waits
    • Introduce data-test attributes; remove waitForTimeout.
  6. Unify configuration
    • Centralize Playwright config and environment handling.
  7. Improve readability
    • Standardize test names, use describe blocks, and clean assertions.
  8. Document and enforce
    • Add a contributor guide and bake patterns into code review.
  9. Prune low‑value tests
    • Drop, merge, or rewrite tests that don’t earn their keep.

By following these steps, you don’t just “move” Playwright tests into Fume—you reshape them into a lean, coherent suite that’s significantly easier to maintain, understand, and scale as your product and team grow.