A page object keeps one screen’s locators and actions in one place, so your tests call its methods instead of repeating the same queries. It’s a design pattern, and it works the same in any test framework. If you’ve written page objects in Playwright or Selenium, this will look familiar.
In e2e, a page object is a plain TypeScript class that takes screen. You
hand it to tests through a fixture.
Write a page object
The constructor takes screen and builds the locators. Locators are lazy, so
nothing is looked up until a method uses one:
Don’t end the file name in .e2e.ts, or the runner collects it as a test.
Hand it out as a fixture
Create the page object in a fixture with test.extend. Every test that
imports this test gets it, with no beforeEach:
Keep it portable
A page object that uses only screen runs on web and mobile targets alike.
When it needs browser (keyboard, CSS selectors, evaluate) or device,
it runs on that platform only.
A fixture that reads browser or device makes every test on that test
need it. On another target, those tests fail in beforeEach with
UNSUPPORTED_CAPABILITY. Give those tests requires: ['browser'] to skip
them there instead, or keep portable tests on the base test.
Where agent goals fit
A method can take agent as an argument and wrap a goal the app changes
often:
Don’t read agent in the fixture. Every test on that test would then need
a configured model, the deterministic ones too.
Many page object methods exist only to click through a flow. One
agent.act goal can replace them. Keep methods for
the steps every test repeats and needs exact.
To start tests signed in, don’t call signIn in each one. Save a session
one time instead. See Signing in.