Classes and OOP basics
A class is a template that keeps data (fields) and behaviour (methods) together. In test automation the main use of classes is the Page Object Model: one class per page, locators as fields, user operations as methods.
The core elements:
class LoginPage { ... }— the declarationconstructor(page)— setup that runs once when the object is created, onnew LoginPage(page)- Methods —
async login(user, pass) { ... } - Fields —
this.email = ... - Private fields —
#token(native JavaScript) orprivate token(TypeScript): inaccessible from outside staticmembers — called on the class without an instance:TestData.defaultUser()
| Construct | What it does | Example in a POM |
|---|---|---|
| constructor(page) | prepares locators when the object is created | this.submit = page.getByRole('button') |
| extends / super() | inherits from another class; super() calls the parent constructor | class LoginPage extends BasePage |
| #privateField | visible only inside the class | #baseUrl — tests cannot touch it |
| static method | called without an instance | TestUser.random() |
| arrow class field | binds this to the instance permanently | get = (path) => fetch(...) |
Inheritance or composition?
Inheritance expresses "X is a kind of Y": LoginPage extends BasePage. The benefit is obvious (shared open(), waitForLoad() in one place), but a deep hierarchy (3+ levels) makes a POM heavy: finding where a method is defined gets hard, and a change in the parent unexpectedly breaks every page.
Composition expresses "X has a Y": inside LoginPage, this.header = new HeaderComponent(page). For reusable UI components (header, modal, table, filter panel) composition is exactly the right choice.
A practical rule: one level of BasePage inheritance plus composition for everything else. That is the balance that works for most teams.
The `this` trap — a very common interview topic. When you detach a method from its object and pass it as a callback (items.map(api.get)), this is lost and becomes undefined. Three fixes: wrap it in an arrow (p => api.get(p)), use api.get.bind(api), or write the class field as an arrow (get = (p) => ...).
A second rule for POMs: page object methods should expose actions, not assertions. Keep assertions in the test file — then reading the test tells you what it verifies.
📚 Sources and documentation
- Classesofficialdeveloper.mozilla.org
- Inheritance and the prototype chainofficialdeveloper.mozilla.org