Component
createComponent makes it possible to describe a reusable interface fragment.
A component defines where it is located, which elements it exposes, which methods it provides, and which sub-components it embeds.
In practice, it is the basic building block to avoid repeating selectors and helpers everywhere in the tests.
Simple example
ts
import { Actions, createComponent, 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']"),
};
},
getMethods({ elements }) {
return {
async fillSearch(value: string) {
await elements.input.fill(value);
},
};
},
},
);
testClient("component example", async({ website }) => {
const component = searchForm(website);
await component.methods.fillSearch("duplojs");
await Actions.click(component, "submitButton");
});What is happening here
- the example shows an extended Playwright client, then the component definition.
createComponent(...)describes asearchFormcomponent.getMainElement(...)defines its root element.getElements(...)exposes reusable named locators.getMethods(...)makes it possible to group a simple business action in the component.
Parameters
name- the public name of the component in the test model.params.getMainElement- returns the root locator of the component.params.getElements?- exposes named elements from the component.params.getMethods?- exposes methods built from the component context.params.components?- registers sub-components accessible from the component.
Syntax
ts
interface CreateComponentParams {
getMainElement(params: {
body: Locator;
}): Locator;
getElements?(params: {
mainElement: Locator;
body: Locator;
}): Record<string, Locator>;
getMethods?(params: {
mainElement: Locator;
body: Locator;
elements: Record<string, Locator> | undefined;
website: Website;
}): Record<string, (...args: any[]) => any>;
components?: ComponentEngine[];
}
type ComponentEngine = (website: Website) => Component;
interface Component {
name: string;
mainElement: Locator;
elements: Record<string, Locator> | undefined;
methods: Record<string, (...args: any[]) => any> | undefined;
iWantToSeeComponent(componentName: string): Promise<Component>;
}What is it for?
Component is used to centralize in the same place:
- the selectors of an interface block
- small reusable business actions
- composition between several interface fragments
In other words, it makes it possible to write more readable and more stable tests when the same interface area appears often.
See also
Website- to instantiate a component in a test context.Page- to build a page on the same model as a component, withmakePath(...)in addition.Component Interaction- to create reusable interactions on a component's elements.Actions- for ready-to-use actions on components.Assertions- for ready-to-use assertions on components.
