Introduction
@duplojs/playwright is a light layer above Playwright to structure a test suite around the tested website.
The idea is not to replace Playwright, but to add a more readable model to it:
- a
Websiteto carry the global test context Pageobjects for navigable screensComponentobjects for reusable interface fragments- business helpers to avoid repeating the same intentions everywhere
What the lib brings
- a stable way to organize the test suite as it grows
- a vocabulary closer to the tested website than to a set of locators
- reusable interactions and assertions
- more expressive Playwright steps in reports
The philosophy
The goal is to test a website with business objects, not to write every test directly against page.locator(...).
In concrete terms, instead of having selectors and helpers scattered across each test, the goal is to:
- centralize the website structure
- reuse frequent behaviors
- keep tests intention-oriented
Minimal example
ts
import { Actions, createComponent, createPage, createWebsite, type Website } from "@duplojs/playwright";
import test from "playwright/test";
interface TestFixtures {
website: Website;
}
const testClient = test.extend<TestFixtures>({
async website({ page, context }, use) {
const website = createWebsite({
playwrightPage: page,
playwrightBrowserContext: context,
envConfig: {
baseUrl: "https://example.com",
},
});
await use(website);
},
});
const SearchForm = createComponent(
"searchForm",
{
getMainElement({ body }) {
return body.locator("[data-search-form]");
},
getElements({ mainElement }) {
return {
input: mainElement.locator("input"),
submitButton: mainElement.locator("button[type='submit']"),
};
},
},
);
const HomePage = createPage(
"home",
{
makePath() {
return "/";
},
getMainElement({ body }) {
return body.locator("main");
},
components: [SearchForm],
},
);
testClient("home page", async({ website }) => {
const homePage = await website.iNavigateTo(HomePage);
const searchForm = await homePage.iWantToSeeComponent("searchForm");
await Actions.fill(searchForm, "input", "superValue");
await Actions.click(searchForm, "submitButton");
});What is happening here
- the extended Playwright client prepares
createWebsite(...)once. createPage(...)describes a navigable screen.- the test gets
websitefrom this client. website.iNavigateTo(...)lets you speak in terms of pages, not only in terms of URLs.
