Migrating from Tosca to Playwright: A Practical Guide
1. Introduction
Migrating from Tosca to Playwright is a big job, and I won't pretend otherwise. Usually there are years of accumulated test cases, testers who know Tosca inside and out, and a suite that's deeply wired into your systems. Looking at all that, it's easy to feel like migration would take forever or break everything along the way.
The good news is that it's very doable if you approach it right. I went through this migration myself on a large enterprise project, so let's walk through it together and I'll show you where it gets tricky and how to handle those parts.
One quick note: all code samples and diagrams in this post are rebuilt and anonymized, but the approach and lessons come from the real project.
2. Why Teams Move Away from Tosca
Let me start with the reasons, because they shape the whole migration plan.
The first one is flexibility. Tosca itself has a pretty complex structure. If you've ever developed tests in it, you know what I mean. Your test lives across modules, test cases, execution lists, and test sheets. Values travel between steps through buffers. When the UI changes, you rescan modules and hope nothing else breaks. And the moment you need something nonstandard, like checking a file on a server or validating a generated document, you end up with custom steps, workarounds, things only one person on the team knows how to maintain. In a code-based framework, all of that is just regular code.
The second one is speed. On my project, a regression run took 3 hours for 25 tests. Three hours, 25 tests. That's a lot of waiting for a small piece of coverage, and when a run fails somewhere in the middle, you often get to wait all over again.
The third one is cost. A single Tosca license is already high, and then you multiply it across the whole QA team, and now you're looking at a serious number every year. To be fair, Tosca is not a bad tool. It's reliable for specific complex projects, but honestly not across most of them. If you're paying that much and still fighting the tool, that's a sign.
If some of this sounds familiar, a migration is worth considering.
3. What Playwright Offers Instead
Playwright is a free open source framework from Microsoft. Tests are written in code, usually TypeScript, and run in Node. That last part matters more than people realize. Since your test is a regular Node program, anything Node can do, your test can do. Upload a file over SFTP, query a database, run a command over SSH, it's all just packages you install and use.
On top of that you get auto-waiting, parallel runs, and traces that show exactly what happened when a test fails. Tests live in Git like any other code, and CI integration is a config file, not a project.
4. Tosca vs Playwright: The Verdict
After working with both, the comparison is honestly not close. Playwright wins on most parameters that matter day to day: it's free, regression runs finish in minutes instead of hours, backend scenarios are regular code instead of custom workarounds, and tests live in Git and CI like any other engineering work.
Tosca keeps one strong card: desktop applications and SAP. If your project leans hard on those, Tosca is still the practical choice there. For web projects, Playwright is simply the stronger tool.
5. Planning the Migration
Before any real planning, do a small proof of concept. Take one real test, ideally an ugly one with backend parts, and build it in Playwright end to end. The goal is to validate the whole concept, not just browser clicks: the application works with Playwright, the test environment lets you reach your servers and databases, files can be moved, reports come out readable. A week spent here answers whether the approach works at all, and everything after it stands on solid ground.
The next step is an audit. Go through the whole Tosca portfolio and write down what each test actually covers, which validation steps it performs, and in which environments it exists. I did this as a simple matrix, one row per test flow, one column per validation step. Something like this:
| Test flow | Input file placed | Job triggered | Status validated | PDF validated | QA | UAT |
|---|---|---|---|---|---|---|
| invoice-letters | yes | yes | yes | yes | x | x |
| claims-notice | yes | yes | yes | no | x | |
| welcome-pack | yes | yes | no | yes | x |
It looks boring, but this table answers the two most important planning questions: what to migrate and what to retire. Some tests will turn out to be duplicates or checks for features that no longer exist. Retire them, don't migrate dead weight.
Then prioritize. Smoke tests and critical paths go first, so the new suite becomes useful as early as possible. Everything else moves in batches after that.
And keep Tosca running the whole time. Both suites run in parallel until the new one proves itself on a few releases. Only then turn the old one off.
6. The Migration Process
This is the core of the work, so let's take it step by step.
Mapping Tosca concepts to Playwright
Most Tosca concepts have a natural home in Playwright, and knowing the mapping saves a lot of confusion at the start:
| Tosca | Playwright |
|---|---|
| Module | Page object or helper function |
| TestCase | Spec file |
| TestSheet / test data | Fixture files and parameterized tests |
| ExecutionList | Projects and tags in the config |
| Buffer | A regular variable |
| Recovery scenario | Retries and cleanup hooks |
Rebuild, don't translate
The mapping helps you find your way around, but don't use it to convert tests step by step. A one-to-one translation gives you Tosca written in TypeScript, with the same clumsy structure and none of the benefits. Instead, take the test's intent, what it actually verifies, and write it the way you'd write it in Playwright from scratch. In my experience the result is usually shorter than the original.
Project structure
Here's the structure that worked for me:
tests/
invoice-letters.spec.ts
claims-notice.spec.ts
helpers/
sftp.ts
jobs.ts
polling.ts
pdf.ts
fixtures/
inputFiles/
templates/
playwright.config.ts
.env
Specs stay thin and readable. All the technical work, file transfers, job triggers, polling, PDF parsing, lives in helpers. Environment data like hosts and credentials goes to .env and the config, never into the tests.
A before and after example
Here's a real flow from my project, anonymized. In Tosca it was a test case with steps spread across modules: a custom step placed an input file on a server, an API step triggered a batch job, then a module checked the job status in an internal portal with the job id passed through a buffer, and another custom step validated the generated PDF. Four different mechanisms, two of them custom, and only one person on the team knew how the custom ones worked.
The same test in Playwright:
// tests/invoice-letters.spec.ts
import { test, expect } from '@playwright/test';
import { uploadInputFile } from '../helpers/sftp';
import { triggerJob } from '../helpers/jobs';
import { waitForJobStatus } from '../helpers/polling';
import { downloadAndParsePdf } from '../helpers/pdf';
test('invoice letters: from input file to printed document', async ({ page, request }) => {
const fileName = `InvoiceRequest_${Date.now()}.xml`;
await uploadInputFile('fixtures/inputFiles/InvoiceRequest.xml', fileName);
const jobId = await triggerJob(request, 'invoice-letters', fileName);
await waitForJobStatus(page, jobId, 'Completed');
const pdf = await downloadAndParsePdf(jobId);
expect(pdf.text).toContain('Invoice Number');
});
The helpers are plain Node code. The SFTP one is a library call:
// helpers/sftp.ts
import SftpClient from 'ssh2-sftp-client';
export async function uploadInputFile(localPath: string, remoteName: string) {
const sftp = new SftpClient();
await sftp.connect({
host: process.env.SFTP_HOST,
username: process.env.SFTP_USER,
password: process.env.SFTP_PASSWORD,
});
await sftp.put(localPath, `${process.env.SFTP_INBOX}/${remoteName}`);
await sftp.end();
}
And the polling helper replaces what used to be the flakiest part of the old suite. No fixed waits, just a retry loop with a limit and a clear error:
// helpers/polling.ts
import { Page } from '@playwright/test';
export async function waitForJobStatus(page: Page, jobId: string, expected: string, retries = 10) {
for (let i = 0; i < retries; i++) {
await page.getByRole('button', { name: 'Refresh' }).click();
const status = await page.getByTestId(`job-${jobId}-status`).textContent();
if (status?.trim() === expected) return;
await page.waitForTimeout(8000);
}
throw new Error(`Job ${jobId} did not reach status "${expected}"`);
}
That's the whole trick. Everything that needed custom engineering in Tosca became a helper file that any engineer on the team can read and fix.
7. Common Pitfalls
Trying to migrate everything at once. Move tests in batches, critical ones first. Flipping the whole suite in one go usually ends with no working regression on either side.
Recreating Tosca's structure instead of adopting Playwright idioms. We covered rebuild versus translate above, but this mistake deserves its own warning because it's the most tempting one. If your spec files start looking like execution lists, stop and rethink.
Waiting until the whole team can code. Some teams put the migration on hold until every tester learns TypeScript, and it never starts. It works better the other way around: a specialist who has done this before builds the suite and the helpers, and the team picks things up along the way by reviewing and maintaining tests that already work.
Neglecting reporting and test management. Tosca had it built in, and nobody notices it's missing until the first big run finishes with nothing to show. Set up a reporter like Allure and, if your process needs it, an integration with a test management tool like TestRail in the first week, not the last.
Ignoring environment drift. A code-based suite is honest, and that cuts both ways. If test environments drift from production, say the database schema changed in QA but not in your fixtures, tests start failing for reasons that have nothing to do with the app, and people burn hours chasing phantom failures. Keep test data and environments versioned and aligned, and when a test fails, check the environment before blaming the test.
8. Results and Metrics
Numbers are the most convincing part of any migration story, so here are mine.
The old regression took 3 hours for 25 tests, running one after another. The same 25 tests in Playwright, running in parallel, finish in about 30 minutes. And most of that time is the system under test itself, batch jobs and document generation, not the framework. That changed how the suite gets used: instead of a run being a scheduled event, it's something you kick off after any change.
Maintenance changed the most in day-to-day life. A broken test used to mean opening Tosca, hunting through modules, and rescanning. Now it means reading a diff and fixing a line of code, usually in minutes. And the suite grew after the migration, because adding a new test stopped being a project.
9. Conclusion
Migration from Tosca to Playwright is a big job, but a very realistic one with the right approach. It worked for my project, and the difference in speed, cost, and day-to-day work made it clearly worth it.
The short version of the whole post:
- Audit the suite, retire dead tests
- Migrate critical paths first, in batches
- Rebuild tests in Playwright patterns, don't translate them
- Put backend work into helpers, environment data into config
- Set up reporting in week one
- Run both tools in parallel until the new suite proves itself
If you're planning a similar migration and want to talk it through, feel free to reach out.