There's a question that struggles me for a long time already and so far I couldn't find or figure out a good answer. Hopefully, you will help me with that, folks.
Before the story, a little background: in test automation, there's one very popular pattern called Page Object Model. Briefly speaking, it's about mapping all essential elements of a single web page on class properties and developing corresponding methods to interact with the page. It looks like this (TypeScript syntax):
class LoginPage {
private driver: WebDriver;
private phoneNumberInput = '[data-test="phone-number"]'
private passwordInput = '[data-test="password"]'
private submitButton = '[data-test="submit"]'
constructor(driver: WebDriver) {
this.driver = driver;
}
async typePhoneNumber(value: string) {
await this.driver.type(this.phoneNumberInput, value);
}
async typePassowrd(value: string) {
await this.driver.type(this.passwordInput, value);
}
async submit() {
await this.driver.click(this.submitButton);
}
async loginAs(phoneNumber: string, password: string) {
await this.typePhoneNumber(phoneNumber);
await this.typePassowrd(password);
await this.submit();
}
}
So, we have a class that represents a login page with three CSS locators mapped on private properties, four public methods to perform some actions, and eventually, we have a private property driver
which is an object that has all the methods allowing to interact with the actual web page.
Easy peasy.
And what's my problem here? Personally, I'm not convinced if driver
should be a class member of LoginPage
, because how this property describes the class? What in common they have with each other? An object of type Webdriver
isn't a part of LoginPage
in the same way as phoneNumberInput
, is it? I feel it'd be "more correct" to pass a driver to each method instead:
class LoginPage {
(...)
async typePhoneNumber(driver: WebDriver, value: string) {
await driver.type(this.phoneNumberInput, value);
}
async typePassowrd(driver: WebDriver, value: string) {
await driver.type(this.passwordInput, value);
}
async submit(driver) {
await driver.click(this.submitButton);
}
async loginAs(driver: WebDriver, phoneNumber: string, password: string) {
await this.typePhoneNumber(driver, phoneNumber);
await this.typePassowrd(driver, password);
await this.submit(driver);
}
}
But even if it seems like the valid approach it makes everything worse. It's simply easier to pass an instance of WebDriver
to the constructor and get over with that.
So, how do think, what's the golden mean here between correctness and convenience? Should the class members always describe the class or there some exceptions from the rules like services?
WebDriver
type and initialize it outside the constructor but it's not the point here, I guess