From MFTF to Playwright for Magento 2 E2E testing
The benefits of modern TypeScript E2E testing over the old XML-and-Selenium way
On this page
For years my end-to-end (E2E) tests on Magento 2 were written in MFTF, the Magento Functional Testing Framework. It ships with the platform and it does the job, but every test meant writing XML driven by Selenium underneath — verbose, slow, and awkward to debug. A while ago I moved that work to Playwright as my MFTF alternative, and I am not going back. Here is what actually changed, feature by feature.
The old way vs the new way
MFTF
the traditional way
- Tests written in XML
- Spread across many files
- Selenium — slower, flakier
- Debug from a screenshot
- Coupled to Magento core
Playwright
the modern way
- Tests written in TypeScript
- One readable spec file
- Auto-waiting, runs in parallel
- Trace viewer time-travel
- An independent dependency
Same goal — "does checkout still work?" — reached two very different ways.
The rest of this post is really just those rows, explained.
XML across many files vs one TypeScript spec
In MFTF a single “add to cart” test is spread over a test file, a section file for the selectors and a data file for the product — all XML, all cross-referenced by string keys:
<!-- Test/Mftf/Test/AddToCartTest.xml -->
<test name="AddSimpleProductToCartTest">
<amOnPage url="{{SimpleProduct.urlKey}}.html" stepKey="goToProduct"/>
<waitForPageLoad stepKey="waitForProductPage"/>
<click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/>
<waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccess"/>
<see userInput="You added Simple Product to your shopping cart." stepKey="assertSuccess"/>
</test>The same test in Playwright is one file, and it reads like the steps a person would take:
test('adds a product to the cart', async ({ page }) => {
await page.goto('/simple-product.html');
await page.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByRole('alert')).toContainText('added');
});No section file, no data file, no stepKey on every line. Your editor autocompletes it, TypeScript catches typos, and
refactoring is a rename instead of a find-and-replace across XML.
Manual waits vs auto-waiting
Notice what is missing from the Playwright version: the waitForPageLoad and waitForElementVisible steps. Playwright
auto-waits — a click waits for the element to be visible, enabled and stable first, and expect(...) retries until
the assertion passes or times out. In MFTF (via Selenium) you add those waits by hand, and forgetting one is the classic
source of a test that passes locally and fails in CI. Most of my flakiness simply disappeared.
A screenshot vs the trace viewer
This one changed how I debug. When an MFTF test fails in CI you typically get a screenshot and a stack trace and are left guessing. Playwright records a trace on the first retry — a full timeline you open locally and scrub through:
npx playwright show-trace trace.zipEvery action, every network call, a DOM snapshot at each step, and the console. Instead of reproducing the failure you
watch it happen. There is also a live UI mode (--ui) for writing tests and a code generator (--codegen) that
writes selectors for you.
Slow and sequential vs fast and parallel
Playwright runs tests in parallel across worker processes out of the box, and the browser automation talks to Chromium directly rather than through the Selenium/WebDriver stack. On the suites I have migrated the wall-clock time dropped sharply — enough that running E2E on every push became realistic rather than a nightly chore.
Clean, isolated test data
The one habit worth keeping from MFTF is its data fixtures — never assume data “is already there”. Playwright makes this elegant with fixtures: each test seeds exactly what it needs through the Magento REST API, uses it, and destroys it afterwards, with no effort from the test author. I run them against a local Warden environment that mirrors production closely.
Each test seeds its own data and destroys it — no shared state, nothing left in the database.
In the spec you never see the plumbing. You just ask for the fixture:
test('shows my orders', async ({ authenticatedPage }) => {
// a brand-new customer was created and logged in for you;
// it is deleted automatically when the test ends.
await authenticatedPage.goto('/customer/account/');
await expect(authenticatedPage.getByRole('heading', { name: 'My Account' })).toBeVisible();
});Not welded to Magento’s release cycle
Because MFTF ships with the platform, its version moves when Magento moves. Playwright is just an npm dev dependency you update on your own schedule, independent of the store’s Magento version. One less thing coupled to the upgrade.
The one thing MFTF still has
Fairness: MFTF ships with a huge library of ready-made action groups and page sections for core flows, and it stays strictly inside Magento’s tooling. If that matters to your team, it is still a defensible choice. For the work I do, none of it outweighed Playwright’s speed, TypeScript specs and trace viewer.
Making it reusable
One extra step paid off across projects: I moved the shared pieces — page objects, fixtures, the API helper, a base config and a CI workflow — into a single internal package that every store installs, so a fix in one place reaches all of them. That reusable shape is inspired by elgentos' magento2-bdd-e2e-testing-suite (MIT) — a great open-source Playwright suite for Magento 2 and Hyvä. If you are testing a single store and want something ready-made, start there.