Home / Services / Playwright framework development

Playwright framework development for the team that will write the tests

The structural decisions you have been deferring, made in your repository, on your application, and then written down so the people who join in March can follow them.

Short answer

We build the Playwright repository your team writes tests in: the structure, the fixtures, the login, the test data, a config per environment, the CI job and the conventions written down. It arrives with reference tests that show each pattern, not with your suite. Engineers are billed hourly, from $50 an hour, minimum one full-time engineer for one month.

You will get questions back before you get a proposal. Nobody here can scope a repository from a paragraph, and the first reply is a list of what we still need to know.

Who this is for

If you want the tests themselves — your journeys covered, written by us — that is Playwright test automation. This page is for the team who will write most of the tests themselves and wants the repository they write them in decided by somebody who does it every week.

You have already chosen Playwright. You are not confused about it and you do not want a tutorial. You have a tests/ folder with somewhere between fifteen and eighty specs, and every one of them opens by driving the sign-in page for itself.

  • A page object copied off a blog, and a second one copied off a different blog, so two files in the same repository disagree about what a page object is.
  • The base URL written out in three places, and a process.env.STAGING ? … : … that somebody added inside a spec in the spring and that now appears in nine of them.
  • No CI job, or one that everybody has learned to re-run, and two more engineers joining next quarter with nothing to point them at.

Playwright has four official bindings and we build in all of them, so the framework is written in whichever language your engineers will still be writing tests in a year from now. That choice is made with you in the first phase; it is not assumed from what we happen to like.

Playwright's test runner ships only with the JavaScript and TypeScript binding: the playwright.config.ts model, projects, fixtures, retries, sharding and the HTML reporter all belong to it. A Python, Java or .NET framework is built on the runner that language already has, pytest or JUnit or NUnit, so several of the files named below have a different equivalent there and a few have none. The language comparison works through it capability by capability.

Two teams should read something else. If the suite already runs and the pipeline is what hurts — a run too slow to sit through, a browser matrix, runners you host yourself — the framework is not your problem and this is not your engagement. And a team of three whose twenty specs pass in four minutes should not buy this: the structure would cost more than the disorder does.

What you get

Everything below is something you can open and read. What any of it contains depends on what your application turns out to need. The file names are the TypeScript ones. In Python, Java or .NET the same jobs land in that language's own files, and its runner does the splitting and the reporting.

The repository, with the files named

A playwright.config.ts with a project per environment, a fixtures/ directory where setup is composed instead of copied, an auth.setup.ts that signs in once and a route for each role the tests need, and the helpers that create test data and remove it again at the end of the run. Where a spec goes, and what separates a fixture from a helper, both have a written answer.

The pipeline that runs it

A workflow file, sharded across machines, with the HTML reporter and the trace artifacts uploaded so that a failure arrives as something a developer can open and step through. It is wiring for the suite being built, not a rescue of a pipeline that has already become the bottleneck.

The conventions, written down

The document your engineers work from after we go. It states where a new spec goes and what it is called, what belongs in a fixture and what belongs in a helper, the locator policy and the test id attribute the config sets, and how an environment gets added. It is the one deliverable here written for your engineers.

The repository arrives with reference specs — one per pattern, enough to prove each piece runs against your application. That is not coverage of your product. The tests that go red when checkout breaks are a separate engagement.

How it works

  1. What the folder holds now

    We read what is there, run it, and find out which of the decisions in it were made and which were inherited. That includes the question you are already asking about the specs you have: some of them survive a re-shaping and keep their assertions, some are re-written against the new fixtures, and a few describe behaviour nobody has today. You give us the repository to read, one environment that is up, and an hour with whoever can say why the current shape is the shape.

  2. One of your specs, moved

    Before anything is written down, one existing spec is re-made under the proposed structure and left running: the fixture it asks for, the project it runs in, the locator policy applied to its selectors. Everything worth disagreeing about surfaces here, on code you already recognise, and the cost of changing a decision at this point is a re-write of one file. You give us review time from an engineer who will have to live with the answer.

  3. The build

    The config with its projects, the fixtures, the setup that signs in, the test-data helpers, the CI workflow, and a reference spec per pattern. Each piece lands as its own reviewable change and merges before the next one is opened, so nothing about the framework lives on a branch your team cannot see.

  4. Handover, against the conventions

    Your engineers write specs from the conventions document while ours are still on the project. Every question that has to be asked out loud is a hole in the document, and it gets filled before anybody leaves.

No phase above carries an elapsed time, and nothing here publishes one. The length is set by things you can count before we start: how many environments the config has to name, whether your application can be seeded through an API or only through the UI, how many roles the tests have to sign in as, whether there is a CI system to hang a job on at all, and how much of the existing tests/ folder is being kept.

The decisions worth showing in code

None of the samples below is lifted from a client repository; all three are written for this page. They are the two decisions a framework build settles before anything else gets written, and a LoginPage class is neither of them.

One suite, every environment, no branch in a test file

The option is declared once, with a default, in the module every spec imports:

// tests/options.ts
import { test as base } from '@playwright/test';

export type TestOptions = {
  tenant: string;
};

export const test = base.extend<TestOptions>({
  // An option, not a fixture: the config sets it per project.
  tenant: ['acme', { option: true }],
});

export { expect } from '@playwright/test';
Three samples on this page, all checked against Playwright 1.62.

The config then states every value it is allowed to take, one project at a time, alongside the baseURL that goes with it:

// playwright.config.ts
import { defineConfig } from '@playwright/test';
import type { TestOptions } from './tests/options';

export default defineConfig<TestOptions>({
  use: {
    // The locator policy, as one line of config.
    testIdAttribute: 'data-qa',
  },
  projects: [
    {
      name: 'staging-acme',
      use: { baseURL: 'https://acme.staging.example.com', tenant: 'acme' },
    },
    {
      name: 'staging-globex',
      use: { baseURL: 'https://globex.staging.example.com', tenant: 'globex' },
    },
    {
      name: 'production-acme',
      use: { baseURL: 'https://acme.example.com', tenant: 'acme' },
    },
  ],
});

And the spec asks for tenant in its argument list, the same way it asks for page:

// tests/billing.spec.ts
import { test, expect } from './options';

test('the invoice list is scoped to the tenant', async ({ page, tenant }) => {
  await page.goto('/billing/invoices');

  await expect(page.getByRole('heading', { name: 'Invoices' })).toBeVisible();
  await expect(page.getByTestId('tenant-name')).toHaveText(tenant);
});

The declaration and the config are one decision in two files, and Playwright's parameterisation documentation is where both halves come from. The option is typed, so a project that sets a value the type does not allow fails at the type check instead of somewhere inside a run. The set of values sits in the config, in the repository, readable by somebody who has never run the suite. The alternative you have today is an environment variable that is set in CI and not on anybody's laptop, and a spec that reads differently depending on it. Nine files branching on process.env are nine files nobody can review, and they are why adding a third environment turns into a project.

The locator policy, as one line

The testIdAttribute line in that config changes the attribute that getByTestId reads, so a team already carrying data-qa in its markup does not have to re-tag the application to start. Which locator wins by default, and when a test id is the right answer instead, is argued on the test automation page. The policy itself gets a home: one line of config and a section of the conventions document, rather than the habits of whoever wrote each spec.

Where this stops

"Framework" means everything to a vendor and nothing to a buyer, so this page draws the edges.

  • This builds the foundation and does not write your suite. The repository arrives with reference specs that demonstrate each pattern. Coverage of your application is a separate scope, agreed separately, and it is the test automation engagement.
  • It is not a mobile app framework. Playwright drives browsers. What we build can run against a mobile browser — a phone-sized viewport, a mobile user agent, touch input. A native iOS or Android application is not something Playwright can drive at all, so if part of your suite is Appium, that part has nowhere to land here and replacing it is not work we take.
  • It is not a load harness. A framework that opened a thousand sessions to put your application under load is not what we would build, whatever the config said. It is not k6, JMeter or Gatling, and no load testing is sold here.
  • WebKit is not Safari, and Internet Explorer is not supported. Playwright's WebKit build is not the branded browser — the browsers documentation says Playwright "doesn't work with the branded version of Safari since it relies on patches" — so what we build covers WebKit and never covers a real iPhone. Internet Explorer sits outside what Playwright supports at all, and no version of this engagement adds it.
  • Security and penetration testing are not offered. Not in this engagement and not in any other one here.

Handover runs against the conventions document, and that is where a framework build ends. Being taught what is in it — why each fixture exists, what a spec may assume, which decisions are already made — is bought separately as training, by the hour, and is not inside this price. If that is what you want, say so in the first email and we will scope it as its own thing rather than fold it into a build. If what you want instead is somebody writing the specs, that is a dedicated engineer.

What it costs

A framework build is scoped and billed in engineer time. Engineers are billed hourly, from $50 an hour, depending on where the engineer sits. The minimum engagement is one full-time engineer for one month.

You can count the scope yourself before you write to us. How many environments the config has to name, because each one is a project, a base URL and a set of credentials. How many roles the tests sign in as, since one is a setup project and four is a design. Whether there is a CI system to hang the workflow on, or whether that is being chosen here too. And how much of the existing tests/ folder is kept, which is the one that varies most between two repositories of the same size.

Read first

  • Playwright fixtures. What a fixture is for, what belongs in one, and why setup composed beats setup copied.
  • The page object model. Both sides of the argument this page refuses to settle for you.
  • Authentication and storage state. Signing in once and reusing it, including the file that must never be committed.
  • Test data. Creating what a test needs and removing it again, instead of seeding a shared environment by hand.
  • TypeScript or Python. The language question, answered by who maintains the suite rather than by benchmarks.

Questions

How is this different from having you write our tests?

This engagement delivers the repository and the reference specs that prove each pattern in it runs. Covering the journeys in your product is the Playwright test automation engagement, which is scoped and bought separately. If your own engineers will write most of the tests, the foundation is the thing to buy; if we will write most of them, start there instead.

What language will the framework be in?

Any of Playwright's four official bindings. TypeScript or JavaScript, Python, Java, .NET — we build in whichever one your engineers will still be writing tests in a year from now, and that gets settled with you in the first phase. Playwright's own test runner ships only with the JavaScript and TypeScript binding, so a framework in Python, Java or .NET is built on pytest, JUnit or NUnit instead, and the config-driven projects, retries and HTML reporter described above have no direct equivalent there. Code on this site is written in TypeScript by default, so the samples above are TypeScript, which says nothing about what your repository would be written in.

Will it use page objects?

We have not published a company position on page objects, and a service page is the wrong place to invent one. What is documented is that Playwright's own fixtures page builds page objects inside fixtures: the example there constructs the page object in the fixture body and hands the constructed object to the test. So the either-or framing the argument usually takes is the wrong axis, and the question worth asking about your application is how much surface each abstraction gets. The decision is made in the first phase and written into the conventions document. There is a separate article that argues both sides of the pattern at length.

Is this a starter template, or is it built for our application?

Built against your application. The environments the config names are your environments, the setup signs in to your identity provider with your roles, the test data comes out of your API or it has to be created some other way, and the directory names follow the shape of your product.

Can our team write tests in it after you leave?

The conventions document exists for exactly that, and it is a deliverable rather than a promise. It states where a new spec goes, what belongs in a fixture and what belongs in a helper, the locator policy and the test id attribute the config sets. Handover is run against it: your engineers write specs from the document while ours are still on the project, and a question they have to ask out loud is a hole in the document that gets filled while there is somebody there to fill it.

What does a framework build cost, and do we need the audit first?

Engineers are billed hourly, from $50 an hour, depending on where the engineer sits. The minimum engagement is one full-time engineer for one month. What it comes to depends on how many environments the suite has to reach, how many roles the tests sign in as, whether there is a pipeline to hang the CI job on, and how much of the existing folder is being kept. On the second half: no. The audit is there for a team that wants to know what it has before it picks a direction. It is optional, it is billed by the hour, and how long it takes depends on the project. The minimum of one full-time engineer for one month does not apply to it.

Two numbers and a language get us started

How many environments the suite has to run against, how many roles it has to sign in as, and which of the four languages your team writes. That is enough to say what the first phase looks like. We do not need an audit to start. If the people to write tests in it are missing too, we place Playwright engineers by the month.