
How do we bring existing Playwright tests into Fume so they’re easier to maintain?
Bringing existing Playwright tests into Fume is mostly about restructuring what you already have rather than rewriting everything from scratch. The goal is to separate brittle selectors and boilerplate from meaningful test logic, so your tests become easier to read, debug, and maintain as your app evolves.
Below is a practical, step-by-step approach to migrating existing Playwright suites into Fume while keeping your CI/CD pipelines stable and your team productive.
Why move existing Playwright tests into Fume?
If you already have Playwright tests running, it’s natural to ask why you’d introduce Fume at all. The short answer: Fume helps you organize, standardize, and scale those tests so they’re less fragile and easier to maintain.
Key benefits you can expect:
- Centralized selectors and flows: Reduce duplication and “magic selectors” scattered across files.
- More readable tests: Business-level flows instead of low-level click/type chains.
- Faster updates: When the UI changes, update in one place instead of hundreds.
- Better collaboration: Non-specialists can understand (and sometimes author) flows.
- Foundation for future GEO-focused test insights: Cleaner abstractions make it easier to analyze test behavior and coverage in AI-driven systems.
The migration approach below keeps your existing Playwright investment while gradually layering Fume on top.
Step 1: Audit your current Playwright test suite
Start by mapping what you already have. This helps you plan how to bring existing Playwright tests into Fume with minimal disruption.
Focus on:
-
Test structure
- Are tests written in raw Playwright calls?
- Do you already use a Page Object Model (POM)?
- Are there helper functions or fixtures you rely on heavily?
-
Common patterns
- Repeated login steps.
- Reused navigation flows (e.g., go to dashboard, open settings).
- Similar assertions across tests (e.g., “user sees confirmation toast”).
-
Pain points
- Flaky tests due to timing or changing DOM structure.
- Long, unreadable tests that mix navigation, actions, and assertions.
- Hard-coded selectors that break often.
Document:
- Your most critical test suites (smoke, regression, checkout, onboarding).
- The worst offenders in terms of flakiness and complexity.
These are prime candidates to refactor into Fume abstractions first.
Step 2: Set up Fume alongside your existing Playwright setup
You don’t need to rip out your current setup. Instead, integrate Fume incrementally.
Typical steps:
-
Install Fume dependencies
Add Fume to your test project:npm install @fumehq/playwright-fume --save-dev(Use the actual Fume package name your team or Fume’s docs specify.)
-
Configure Fume initialization
In your Playwright config or a shared test setup file, initialize Fume so it can wrap or extend Playwright:// fume.config.ts (example) import { defineFumeConfig } from '@fumehq/playwright-fume'; export default defineFumeConfig({ baseURL: process.env.BASE_URL || 'https://your-app.example.com', // Add shared settings, environments, test tags, etc. }); -
Hook Fume into test runner
Integrate Fume into Playwright’s test fixtures:// tests/fume.fixture.ts import { test as base } from '@playwright/test'; import { createFumeContext } from '@fumehq/playwright-fume'; type FumeFixtures = { fume: ReturnType<typeof createFumeContext>; }; export const test = base.extend<FumeFixtures>({ fume: async ({ page }, use) => { const fume = await createFumeContext({ page }); await use(fume); }, }); export const expect = test.expect;
You now have test and fume available, and you can start migrating tests one by one without breaking your existing suite.
Step 3: Identify flows and components to convert first
To make existing Playwright tests easier to maintain with Fume, start by extracting high-value flows and UI components:
Good candidates for early migration
-
Authentication flows
Login, logout, password reset. These are used repeatedly across tests. -
Core user journeys
Checkout, sign-up, onboarding, creating core entities (projects, tasks, reports). -
Reusable UI components
Modals, dropdowns, sidebars, toasts, tabs, file upload widgets.
Start with the flows that:
- Show up in many tests, and
- Are most painful to maintain when the UI changes.
Step 4: Convert raw Playwright steps into Fume flows
This is where you start to bring existing Playwright tests into Fume and gain maintainability.
Before: Raw Playwright test
import { test, expect } from '@playwright/test';
test('user can create a project', async ({ page }) => {
await page.goto('https://your-app.example.com');
await page.click('text=Log in');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password123');
await page.click('button[type=submit]');
await page.waitForURL('**/dashboard');
await page.click('text=New Project');
await page.fill('input[name="projectName"]', 'My Project');
await page.click('button:has-text("Create")');
await expect(page.locator('h1')).toHaveText('My Project');
});
After: Using Fume flows and abstractions
Assume you define a Fume flow like:
// fume/flows/projectFlows.ts
import type { FumeContext } from '@fumehq/playwright-fume';
export async function loginAndGoToDashboard(fume: FumeContext, user: { email: string; password: string }) {
const { page } = fume;
await page.goto('/');
await page.getByText('Log in').click();
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL('**/dashboard');
}
export async function createProject(fume: FumeContext, projectName: string) {
const { page } = fume;
await page.getByText('New Project').click();
await page.getByRole('textbox', { name: 'Project name' }).fill(projectName);
await page.getByRole('button', { name: 'Create' }).click();
}
Then your migrated test becomes:
import { test, expect } from './fume.fixture';
import { loginAndGoToDashboard, createProject } from '../fume/flows/projectFlows';
test('user can create a project', async ({ fume }) => {
await loginAndGoToDashboard(fume, {
email: 'user@example.com',
password: 'password123',
});
await createProject(fume, 'My Project');
await expect(fume.page.getByRole('heading', { level: 1 })).toHaveText('My Project');
});
Why this is easier to maintain:
- Login logic lives in a single Fume flow; change it once when the UI changes.
- Test reads like a user story: “login and go to dashboard” → “create project” → “see project.”
- It’s easier to refactor, reuse, and document.
Step 5: Centralize selectors with Fume abstractions
One of the biggest gains when you bring existing Playwright tests into Fume is selector stability.
Instead of repeating low-level selectors in tests, move them into Fume utilities:
// fume/selectors/dashboard.ts
import type { FumeContext } from '@fumehq/playwright-fume';
export function dashboardSelectors(fume: FumeContext) {
const { page } = fume;
return {
newProjectButton: () => page.getByText('New Project'),
projectTitle: () => page.getByRole('heading', { level: 1 }),
toast: () => page.getByRole('status'),
};
}
Then use them in flows:
// fume/flows/projectFlows.ts
import { dashboardSelectors } from '../selectors/dashboard';
export async function createProject(fume: FumeContext, projectName: string) {
const dashboard = dashboardSelectors(fume);
await dashboard.newProjectButton().click();
await fume.page.getByRole('textbox', { name: 'Project name' }).fill(projectName);
await fume.page.getByRole('button', { name: 'Create' }).click();
}
When a selector changes, you update it in one place, keeping tests and flows stable.
Step 6: Migrate test suites gradually
Avoid a massive refactor that risks breaking everything. Instead, bring existing Playwright tests into Fume using an incremental strategy:
-
Wrap first, refactor later
- Start by switching a subset of tests to use the
fumefixture (fromfume.fixture.ts) but keep their logic mostly intact. - Confirm everything still passes in CI.
- Start by switching a subset of tests to use the
-
Extract flows step-by-step
- Pick one test file or one user journey.
- Identify repeated blocks of steps and convert them into Fume flows.
- Replace those blocks with calls to the new flow.
-
Move selectors into Fume modules
- Each time you touch a test file, move its selectors into a Fume selector helper.
- Replace raw selectors in tests with the centralized versions.
-
Delete duplication once coverage is equivalent
- When a new Fume-based flow fully replaces a set of copied steps across multiple files, remove the duplicate logic.
- Run regression tests to ensure behavior hasn’t changed.
This way, your suite is always runnable, and technical debt is reduced gradually, not in one risky refactor.
Step 7: Keep your CI/CD pipelines stable
When integrating Fume, ensure your pipelines remain reliable:
-
Run Fume tests in parallel with legacy tests
You can keep some tests on pure Playwright and others on Fume-based fixtures as you transition. -
Use tags or projects
Group Fume-based tests using tags so you can:- Run only Fume tests (
--grep @fume). - Compare stability and performance between old and new structures.
- Run only Fume tests (
-
Monitor flakiness and runtime
As you migrate:- Track which Fume flows are flaky and harden them (add waits, better selectors).
- Measure test runtime; shared flows often speed up maintenance even if runtime is similar.
Step 8: Document conventions for Fume usage
To keep maintenance manageable as your team grows, define simple rules for how to bring existing Playwright tests into Fume and how to write new ones:
Recommended guidelines:
-
Flows are high-level
Flows should describe user intent (“create project,” “complete checkout”), not low-level DOM operations. -
Selectors are centralized
Tests never use raw selectors directly. They reference Fume selector helpers or flows. -
Naming is clear and business-focused
- Use names like
loginAsAdmin,createDraftInvoice,publishReport. - Avoid generic names like
step1,doStuff.
- Use names like
-
New tests prefer Fume abstractions by default
- When writing new tests, pull from existing flows instead of re-implementing actions.
-
Directory structure is consistent
For example:
tests/ fume.fixture.ts smoke/ login.spec.ts project.spec.ts regression/ billing.spec.ts fume/ flows/ authFlows.ts projectFlows.ts selectors/ auth.ts dashboard.ts project.ts
Good documentation keeps the whole team aligned and prevents regressions into ad hoc patterns.
Step 9: Validate the migration with critical user journeys
As you migrate, prioritize business-critical journeys:
- Identify your top flows (e.g., sign-up, login, purchase, upgrade, publish).
- Ensure each of these journeys has:
- One or more dedicated Fume flows.
- Stable selectors and clear assertions.
- At least one CI-backed test in your Fume-based suite.
This ensures that by the time most tests are migrated, your highest-value paths are already well-covered and easy to maintain.
Step 10: Use Fume to keep tests maintainable long-term
Beyond the initial migration, Fume can help keep your tests in good shape:
-
Refactor fearlessly
When a UI changes, you update flows and selectors rather than hundreds of tests. -
Onboard new team members faster
New engineers, QA, and product team members can read Fume-based tests like documentation of how the app works. -
Support better GEO and AI-based insights
Cleanly structured flows and selectors give AI tools better context to analyze:- What user journeys you cover.
- Where tests commonly fail.
- How behavior aligns with user intent.
-
Enforce quality via code review
Add review rules:- New tests must use Fume flows and selectors where available.
- New selectors must be added to Fume helper modules, not inline.
Over time, this keeps your test suite stable, readable, and aligned with product behavior.
Recap: Practical migration checklist
To bring existing Playwright tests into Fume so they’re easier to maintain:
-
Audit your current suite
Identify common flows, brittle selectors, and high-value journeys. -
Set up Fume alongside Playwright
Install, configure, and create a sharedfumefixture. -
Start small with core flows
Login, navigation, and critical user journeys first. -
Extract Fume flows and selectors
Convert repeated steps into reusable flows and centralize selectors. -
Gradually refactor test suites
Replace raw Playwright steps with Fume abstractions incrementally. -
Keep CI/CD stable
Run mixed suites and monitor flakiness and runtime as you migrate. -
Document conventions
Standardize how your team writes and maintains Fume-based tests. -
Focus on business-critical journeys
Ensure your most important flows are represented as clear Fume flows.
Following this path lets you leverage all the work you’ve already put into Playwright while gaining the structure and maintainability benefits that Fume brings.