Skip to content

Migrating an Existing Repo

silverlunah edited this page Sep 3, 2026 · 6 revisions

Migrating an Existing Repo

You already have a Playwright or Cucumber suite. You do not need to rewrite it, restructure it, or move your specs into a Plum scaffold.

Plum handles the rest itself: the report format, the step reporter, browser project names, dependencies, and the browsers your own runner version needs. What is left is one line for Playwright and one for Cucumber, both below, and both only for session replay — reports and per-step timings need nothing from your code. Writing Tests lists exactly what Plum asks of a test and what it does not.


Put the repo where Plum can see it

Plum bind-mounts one tree into its container: projects/. Your tests have to live under it, and the order matters.

1. Create the project in Plum first. Settings → Projects, or the first-run setup. That gives you projects/<slug>/ with a scaffolded tests/ folder.

2. Delete that scaffolded tests/ folder. You are bringing your own.

3. Clone your repo one level inside projects/<slug>/:

plum/
└─ projects/
   └─ <slug>/
      └─ app/              ← your repo, cloned here
         ├─ package.json
         ├─ playwright.config.ts
         └─ tests/

The extra level is required, not stylistic. The folder you point Plum at has to be the one holding your runner config, and that setting cannot be .: an empty value or . falls back to tests. So a repo whose config sits at its own root has to live one level down.

4. Set Settings → Project → Tests folder to that subpath (app above). It is always relative to projects/<slug>/; absolute paths and .. are rejected. A project with a custom tests folder is never scaffolded.

5. Restart once so Plum fills in the recording files it needs.

Nesting deeper is fine. A monorepo works, and dependency resolution walks up, so a root node_modules is found from a nested tests folder. If your tests folder has no package.json of its own, Plum uses what the repo above installed.


What Plum does for you

  • Results. The report format is passed on the command line, so your config needs no reporter entry. A Plum run uses Plum's reporters, which means another reporter declared in your config is not written during one; your own npx playwright test is unaffected.
  • Browsers. --project is only passed when your config declares a project by that name, so a config naming Desktop Chrome, or declaring none at all, still runs.
  • Your runner version. Browser builds are tied to it and the image ships only Plum's, so if your repo pins a different version Plum downloads the browsers that version needs on the first run. Expect that run to take a couple of minutes; later runs reuse them. You do not have to match Plum's version.
  • Dependencies. Installed from your tests folder's package.json before a run, or inherited from the repo root.
  • Recording files. The restart in step 5 fills in the session-recording files your framework needs and nothing else. Existing files are never overwritten; package.json, tsconfig.json and your runner config are skipped when the tree above already provides them. Plum will not scaffold over a folder that already contains tests.

Playwright: the one edit

Session replay needs test to come from Plum's fixture, because overriding the browser context is the only way to inject the recorder. Everything else stays vanilla: expect, Page, Locator, test.step, test.describe, your own fixtures, your config.

If your specs import @playwright/test directly, change the import in each one:

- import { test, expect } from '@playwright/test';
+ import { expect } from '@playwright/test';
+ import { test } from '../fixtures/plum';

expect, Page, Locator and everything else keep coming from @playwright/test. Only test moves.

If you already have your own fixtures, which most mature repos do, do not rewrite your specs. Change the base of your own fixture file, one line, and every spec keeps its current import:

  // tests/fixtures/test.ts
- import { test as base } from '@playwright/test';
+ import { test as base } from './plum';
  export const test = base.extend<{ login: LoginPage }>({ … });

Check which case you are in before editing anything: if your specs import test from a local module, that module is the only file to change.

Steps

Use Playwright's own test.step and the report shows a row per step:

await test.step('open the store', async () => { await page.goto('/'); });

Plum reads them through a reporter of its own, which it adds to the run itself, so your specs need nothing but test.step — including the ones you put in a beforeEach, which Playwright's JSON report leaves out.


Cucumber: the config edits

Cucumber runs with no config file at all, on its defaults. If your repo has none and you want session replay, create one, because only a config can load the recording hooks:

// cucumber.js
module.exports = {
  default: {
    requireModule: ['ts-node/register'],
    require: ['features/**/*.js', 'utils/hooks.ts']
  }
};

That needs ts-node and typescript in your devDependencies, since the hooks are TypeScript.

Cucumber has no fixture equivalent, so recording is wired through hooks, which your cucumber.js has to load.

  1. Require the hooks. Plum puts utils/hooks.ts, utils/recorder.ts and utils/world.ts in your tests folder on restart, but only your config can load them:

    - require: ['steps/**/*.ts'],
    + require: ['steps/**/*.ts', 'utils/hooks.ts'],

    Plum's Before hook opens a recorded browser and assigns it onto whatever World your scenarios already use, so this.page, this.context and this.browser are available in your steps without giving up a World of your own. If you already open a browser yourself, drop that hook and use Plum's, or nothing gets recorded. utils/world.ts is only needed if you want the types; it is yours to edit.

  2. Honour the browser choice. Plum passes the selected browser as BROWSER. If your launch code hardcodes chromium.launch(), the picker in the UI does nothing, whatever the report says. Read it:

    const name = (process.env.BROWSER || 'chromium').toLowerCase();
  3. Set retry: 0. Plum re-runs failed scenarios itself using the project's max-retries setting. A retry in your config compounds with it.


Tags

Tags are only needed to link automation to Test Repository cases and to run a single case from the UI. Plain runs and reports work untagged.

Give every test or scenario its own tag. Tags are identities: two tests sharing one cannot be told apart in a report or matched to separate cases. A Cucumber Scenario Outline shares its tag across every row.

test('User can log in', { tag: '@TC-001' }, async ({ page }) => { … });
@TC-001
Scenario: User can log in

Checklist

Playwright Cucumber
create the project in Plum, delete its scaffolded tests/ yes yes
clone your repo one level inside projects/<slug>/ yes yes
set the tests folder to the folder holding your config yes yes
restart once so Plum adds its files yes yes
reporter / format in your config not needed not needed
browser project names not needed not needed
match Plum’s runner version not needed not needed
import test from the fixture one line per file, or one line total n/a
load the hooks in your config n/a one line
read BROWSER in your launch code n/a if you want the picker to work
retry: 0 n/a recommended
tags only for Test Repository linking only for Test Repository linking

If a run comes back with no scenarios, check the run log first: it names the reason.

Clone this wiki locally