Playwright Framework Build-Up

Detailed interactive revision guide for the framework build completed so far β€” centered on TC102 Correct Login.

1

Framework build sequence

OrderComponentPurpose
1constants/AppConstants.jsCentral place for common constants such as Base URL.
2utils/ScreenshotHelper.jsReusable screenshot functionality.
3hooks/hooks.jsRepeated before/after test behavior.
4testdata/loginData.jsonSeparate test data from test logic.
5pages/HomePage.js + LoginPage.jsPage-specific locators and methods.
6utils/JsonReader.jsReusable access to JSON test data.
7utils/CommonMethods.jsGeneric reusable actions and checks.
8fixture/baseFixture.jsCreates POM objects and injects them into tests.
9tests/loginTestUpdated.spec.jsReadable TC102 business flow.
MOST IMPORTANT REVISION

πŸ”— Framework Connectivity β€” Who Imports What, and Why?

This is the section to revisit whenever the framework feels confusing. A framework is not just a collection of folders. The files are connected through exports β†’ imports β†’ object creation β†’ method calls β†’ returned data.

1. The Big Picture

AppConstants.js

Exports shared constants
module.exports = { BASE_URL }
import / require

hooks.js

Imports the URL + utilities
require('../Constants/AppConstants')

loginData.json

Stores test data
validUser.email
validUser.password
read data

JsonReader.js

Reads and exposes the data
JsonReader.login()

CommonMethods.js

Exports generic static methods
module.exports = CommanMethods
import / require

HomePage.js / LoginPage.js

Reuse generic actions
CommanMethods.clickButton()
CommanMethods.type()

HomePage.js

Exports HomePage class
module.exports = HomePage
import + new

baseFixture.js

Creates the POM object
new HomePage(page)

LoginPage.js

Exports LoginPage class
module.exports = LoginPage
import + new

baseFixture.js

Creates the POM object
new LoginPage(page)

baseFixture.js

Exports custom test + expect
exports.test = base.test.extend(...)
import

loginTestUpdated.spec.js

Receives fixtures
async ({ homePage, loginPage })

2. The β€œExport β†’ Import β†’ Use” Rule

EXPORT = β€œI am making this available.”
module.exports = HomePage;

HomePage.js makes its HomePage class available to another file.

IMPORT = β€œI want to use what another file made available.”
const HomePage = require('../pages/HomePage');

baseFixture.js receives that exported class and can create an object from it.

HomePage.js
β†’
module.exports = HomePage
β†’
require('../pages/HomePage')
β†’
new HomePage(page)
β†’
homePage fixture
β†’
spec
Beginner rule: Whenever you see require(), ask: β€œWhich file is being imported, and what did that file export?” Then ask: β€œWhat am I going to do with the imported class/function/data?”

3. Exactly How TC102 Moves Through the Framework

β‘  Test starts
loginTestUpdated.spec.js imports test and expect from baseFixture.js.
β‘‘ Fixture prepares POMs
baseFixture.js imports HomePage and LoginPage, then creates new HomePage(page) and new LoginPage(page).
β‘’ Fixture injects them
await use(homePage) and await use(loginPage) make those prepared objects available to the test as { homePage, loginPage }.
β‘£ Test gets data
The spec calls JsonReader.login(). JsonReader obtains the values from testdata/loginData.json.
β‘€ Test calls HomePage
homePage.verifyHomePage() and homePage.clickSignupLogin() execute methods belonging to the HomePage object.
β‘₯ POM calls CommonMethods
When LoginPage needs to type or click, it calls CommanMethods.type() or CommanMethods.clickButton().
⑦ CommonMethods performs the generic action
The helper checks whether the locator is enabled, then performs fill() or click().
β‘§ Assertions verify results
POM verification methods use Playwright assertions such as expect(locator).toBeVisible().

4. Method-Level Connection Map

homePage.clickSignupLogin()
β†’
CommanMethods.clickButton(locator)
β†’
checkButtonEnabled(locator)
β†’
locator.click()
loginPage.userLogin(email,password)
β†’
CommanMethods.type(emailTextbox,email)
β†’
checkTextBoxEnabled()
β†’
emailTextbox.fill(email)
JsonReader.login()
β†’
loginData.json
β†’
loginData.validUser
β†’
userLogin(email,password)

5. What β€œStatic” Changes

// Because this is static:
CommanMethods.clickButton(locator);

// NOT:
const methods = new CommanMethods();
methods.clickButton(locator);
Why? CommonMethods does not need an individual object's state for these generic operations. The locator and value are supplied as arguments, so the utility method can be called directly through the class name.

6. What β€œInstance Method” Means in a POM

const homePage = new HomePage(page);

await homePage.clickSignupLogin();

                    ↓

this.signupLoginButton
                    ↓
the locator stored when HomePage(page) was created
Why POM methods are not static here: the POM object carries page-specific locator state through this. new HomePage(page) creates that object and connects its locators to the current Playwright page.

7. The One Mental Model to Remember

EXPORT
β†’
IMPORT
β†’
CREATE / RECEIVE
β†’
CALL METHOD
β†’
PLAYWRIGHT ACTION
β†’
ASSERT RESULT

When confused about a framework file, trace the arrow backwards and forwards: Who exported this? Who imported it? Who created it? Who calls it? What does it call next?

2

Constants β€” AppConstants.js

Store shared values once instead of hard-coding them throughout tests.

const BASE_URL = 'https://www.automationexercise.com/';

module.exports = { BASE_URL };
Key point: One place to maintain common constants such as the application URL.
3

ScreenshotHelper.js

Centralizes screenshot logic so tests do not duplicate screenshot code.

class ScreenshotHelpers {
    static async capture(page, name) {
        await page.screenshot({ path: `screenshots/${name}.png` });
    }
}
module.exports = ScreenshotHelpers;
Key point: Reusable utility logic belongs in a utility class rather than being repeated in every test.
4

Hooks β€” hooks.js

beforeAll
Suite-start logging.
beforeEach
Open the application before each test.
afterEach
Failure screenshot + completion logging.
afterAll
Suite-end logging.
test.beforeEach(async ({ page }) => {
    await page.goto(AppConstants.BASE_URL);
});

test.afterEach(async ({ page }, testInfo) => {
    if (testInfo.status !== testInfo.expectedStatus) {
        await ScreenshotHelpers.capture(
            page,
            testInfo.title.replace(/\s+/g, "_")
        );
    }
});
Key point: Hooks remove repeated setup/teardown from individual tests.
Remember: An afterEach log saying β€œCompleted” does not mean the test passed; afterEach can run after failure.
5

Test data β€” loginData.json

TC102 login credentials live outside the spec.

{
  "validUser": {
    "email": "valid-email",
    "password": "valid-password"
  },
  "invalidUser": {
    "email": "invalid-email",
    "password": "invalid-password"
  }
}
Key point: Test input is separated from test logic so data can change without rewriting the scenario.
6

Page Object Model β€” HomePage + LoginPage

Each POM owns locators and page-specific actions/verifications.

HomePage
  • Signup / Login
  • Home title
  • Logged-in username
  • Delete Account
  • ACCOUNT DELETED!
LoginPage
  • Login heading
  • Email textbox
  • Password textbox
  • Login button
Key point: POM methods remain instance methods because they use locators stored through this.
7

JsonReader.js

The spec should not contain JSON-reading implementation details.

class JsonReader {
    static login() {
        // read loginData.json
        // return login data
    }
}
module.exports = JsonReader;
Key point: The test simply asks JsonReader.login() for login data.
8

CommonMethods.js

Generic actions are centralized here so multiple POMs can reuse them.

class CommanMethods {

    static async checkButtonEnabled(locator) {
        await expect(locator).toBeEnabled();
    }

    static async checkTextBoxEnabled(locator) {
        await expect(locator).toBeEnabled();
    }

    static async clickButton(locator) {
        await CommanMethods.checkButtonEnabled(locator);
        await locator.click();
    }

    static async type(locator, value) {
        await CommanMethods.checkTextBoxEnabled(locator);
        await locator.fill(value);
    }
}
module.exports = CommanMethods;
Key point β€” STATIC: CommonMethods are utility methods, so they can be called directly with the class name: CommanMethods.clickButton(locator). No new CommanMethods() is needed for these static methods.
Key point β€” ENABLED CHECK: clickButton() checks the button is enabled before clicking. type() checks the textbox is enabled before filling. POMs do not need to call those checks separately.
LoginPage.userLogin()
   ↓
CommanMethods.type(...)
   ↓
checkTextBoxEnabled(...)
   ↓
locator.fill(...)

LoginPage.userLogin()
   ↓
CommanMethods.clickButton(...)
   ↓
checkButtonEnabled(...)
   ↓
locator.click()
Do not mix these concepts: CommonMethods can be static utility methods; POM methods stay instance methods because they use this.locator.
9

Custom fixture β€” baseFixture.js

The fixture moves repeated POM object creation out of every spec.

const base = require('@playwright/test');

const LoginPage = require('../pages/LoginPage');
const HomePage = require('../pages/HomePage');

exports.test = base.test.extend({

    loginPage: async ({ page }, use) => {
        const loginPage = new LoginPage(page);
        await use(loginPage);
    },

    homePage: async ({ page }, use) => {
        const homePage = new HomePage(page);
        await use(homePage);
    }
});

exports.expect = base.expect;
page
β†’
new HomePage(page)
β†’
homePage fixture
page
β†’
new LoginPage(page)
β†’
loginPage fixture
Key point: use(value) hands the prepared POM object to the test. The spec no longer needs new HomePage(page) or new LoginPage(page).
10

Spec file β€” TC102

const { test, expect } = require('../fixture/basefixture');
const JsonReader = require('../utils/JsonReader');
const Hooks = require('../hooks/hooks');

test('TC102 - Login User with correct email and password @smoke @login',
    async ({ homePage, loginPage }) => {

        const loginData = JsonReader.login();

        await homePage.verifyHomePage();
        await homePage.clickSignupLogin();
        await loginPage.verifyLoginPage();

        await loginPage.userLogin(
            loginData.validUser.email,
            loginData.validUser.password
        );

        await homePage.verifyLoggedInUser();
        await homePage.userDeletion();
        await homePage.verifyAccountDeleted();
    }
);
Key point: The spec expresses the business flow. Fixture supplies the POMs, JsonReader supplies data, POMs perform page-specific work, and CommonMethods perform generic actions.
11

Complete execution flow

TC102 Spec
   ↓
Custom test from baseFixture
   ↓
Fixtures create HomePage + LoginPage objects
   ↓
Hooks provide common setup/teardown
   ↓
JsonReader provides login data
   ↓
Spec calls POM methods
   ↓
POM calls CommonMethods where appropriate
   ↓
CommonMethods performs reusable Playwright actions
   ↓
Browser / Automation Exercise
   ↓
Assertions verify the result
12

Key rules to memorize

ConceptRule
CommonMethodsGeneric reusable actions/checks.
Static utility methodsCall with class name; no utility instance needed.
POM methodsInstance methods because they use this.
FixturePrepares reusable objects and injects them into tests.
use()Passes the prepared fixture value to the test.
JSONSeparates test data from test logic.
JsonReaderProvides reusable access to JSON data.
HooksHandle repeated setup/teardown.
SpecDescribes the test/business flow rather than implementation details.
Milestone: TC102 Correct Login has been successfully executed using this framework build.