A new engineer joins a Playwright team and writes a useful test on day one. A new engineer joins a Selenium team and takes three weeks, because before writing a test they must learn the team's private framework.
That framework is unavoidable. Selenium gives you browser control and nothing else: no runner, no assertions, no config, no waiting, no reporting. Every Selenium organisation builds the same six layers, and building them well is the core senior skill for this tool.
The six layers, bottom up.
tests/ what the business cares about
contacts/create.test.js
pages/ screen and component models
ContactsPage.js
components/ reusable widgets
DataGrid.js
support/
waits.js the wait library, layer 3
driver-factory.js lifecycle, layer 2
api-client.js fast arrangement
data.js unique test data
config/ layer 1
index.js
Layer one: configuration. One module owning every environment-dependent value. Covered in the intermediate track; at senior level the addition is that nothing else may read process.env. When environment access is scattered, you cannot answer "what does this suite need to run?" without grepping.
Layer two: the driver factory. One function that builds drivers, applying options, headless mode, window size, timeouts and the Grid URL. Discussed in the previous lecture. Its value is that it makes all of those one-line changes.
Layer three: the wait library. The most important layer, and the one covered in its own lecture next. Selenium has no automatic waiting, so if every engineer writes waits individually, every engineer writes subtly different and subtly wrong waits.
Layer four: components and pages. Modelling the application. Two lectures on this later, because getting the model wrong is what makes large Selenium suites unmaintainable.
Layer five: data and API access. Unique data builders, plus an HTTP client for fast arrangement. Selenium has no HTTP client, so this is axios or fetch next to it, and that is normal rather than a hack.
Layer six: the tests. If the lower layers are right, tests read as behaviour and contain no selectors, no waits and no driver management.
The rule that keeps the layers honest: dependencies point downward only. Tests use pages, pages use components and waits, everything uses config. A page object that imports from a test file, or a wait helper that knows about a specific screen, means the layering has collapsed and the whole thing will drift back into a pile.
What a good test looks like when this is done.
describe('contacts', () => {
it('deleting a contact removes it from the list', async () => {
const contact = await api.createContact(uniqueContact());
await contactsPage.open();
await contactsPage.delete(contact.name);
expect(await contactsPage.hasRow(contact.name)).to.be.false;
});
});
No driver, no selectors, no waits, no cleanup. Everything the framework should absorb has been absorbed. Compare that to raw Selenium and the value of the six layers is obvious.
Onboarding is a design constraint, not an afterthought. Because new engineers must learn your private framework, its discoverability is part of its quality. Two things pay for themselves quickly: a README showing how to write one test end to end, and consistent naming so an engineer can guess the method they need rather than searching.
The failure mode to avoid. Over-abstracting. A framework with fifteen layers of inheritance, where finding what a test actually does means opening nine files, is worse than a slightly repetitive suite. The test: can a new engineer read a spec and correctly predict what the browser does? If not, you have built a puzzle.
Remember this: six layers, dependencies pointing downward, and a test that contains only behaviour.
Reference: Page object models.