The UI of your web application has interaction areas which can be shared across different tests.
To avoid code duplication you can put common locators and methods in one place.
All objects described here are injected via Dependency Injection, in a similar way AngularJS does. If you want an object to be injected in a scenario by its name, you can add it to the configuration:
include:{
I:"./custom_steps.js",
Smth:"./pages/Smth.js",
loginPage:"./pages/Login.js",
signinFragment:"./fragments/Signin.js"
}
These objects can now be retrieved by the name specified in the configuration.
Required objects can be obtained via parameters in tests or via a global inject() call.
During initialization, you were asked to create a custom steps file. If you accepted this option, you are now able to use the custom_steps.js file to extend I. See how the login method can be added to I:
exportdefaultfunction(){
returnactor({
login:function(email, password){
this.fillField('Email', email);
this.fillField('Password', password);
this.click('Submit');
}
});
}
Instead of I you should use this in the current context.
If an application has different pages (login, admin, etc) you should use a page object.
CodeceptJS can generate a template for it with the following command:
Terminal window
npxcodeceptjsgpo
This will create a sample template for a page object and include it in the codecept.json config file.
Page objects should be classes. Use const { I } = inject() at the top of the file to access I and other page objects. Export the class itself — the DI container will auto-instantiate it.
The inject() call at the top returns a lazy proxy. I and other page objects resolve at call time, so it’s safe to destructure before class definition.
You can include this pageobject in a test by its name (defined in codecept.conf.js). If you created a loginPage object,
it should be added to the list of arguments to be included in the test:
Page objects can also be extended via class inheritance:
const{I}=inject();
classAttachFile{
inputFileField ='input[name=fileUpload]'
fileSize ='.file-size'
fileName ='.file-name'
asyncattachFileFrom(path){
awaitI.waitForVisible(this.inputFileField)
awaitI.attachFile(this.inputFileField, path)
}
asynchasFileSize(fileSizeText){
awaitI.waitForElement(this.fileSize)
const size =awaitI.grabTextFrom(this.fileSize)
expect(size).toEqual(fileSizeText)
}
}
// Export class for auto-instantiation
exportdefault AttachFile
While building complex page objects it is important to keep all async functions to be called with await. While CodeceptJS allows to run commands synchronously if async function has I.grab* or any custom function that returns a promise it must be called with await. If you see UnhandledPromiseRejectionWarning it might be caused by async page object function that was called without await.
Similarly, CodeceptJS allows you to generate PageFragments and any other abstractions
by running the go command with --type (or -t) option:
Terminal window
npxcodeceptjsgo--typefragment
Page Fragments represent autonomous parts of a page, like modal boxes, components, widgets.
Technically, they are the same as PageObject but conceptually they are a bit different.
For instance, it is recommended that Page Fragment includes a root locator of a component.
Methods of page fragments can use within block to narrow scope to a root locator:
const{I}=inject();
classModal{
root ='#modal'
accept(){
within(this.root,function(){
I.click('Accept');
});
}
}
exportdefault Modal
To use a Page Fragment within a Test Scenario, just inject it into your Scenario:
StepObjects represent complex actions which involve the usage of multiple web pages. For instance, creating users in the backend, changing permissions, etc.
StepObject can be created similarly to PageObjects or PageFragments:
Terminal window
npxcodeceptjsgo--typestep
Technically, they are the same as PageObjects. StepObjects can inject PageObjects and use multiple POs to make a complex scenarios:
Page objects can also be used to manage test data via API. Data Objects are page object classes that create data using the REST helper and automatically clean it up via the _after() hook.
Scenario('user sees their profile',async({I, userData })=>{
const user =await userData.createUser({ name:'John Doe'});
I.amOnPage(`/users/${user.id}`);
I.see('John Doe');
// userData._after() runs automatically — deletes the created user
});
This is useful for tests that need API-created data with automatic cleanup, without the overhead of factory configuration.
Learn more: See Data Objects for complete documentation, examples, and configuration.
Sometimes you need to use a page object in only one or a few tests without adding it to global configuration. Use injectDependencies() to inject page objects dynamically per test:
pages/searchPage.js
const{I}=inject();
classSearchPage{
constructor(){
this.searchField ='#search-input';
this.searchButton ='button[type=submit]';
}
search(query){
I.fillField(this.searchField, query);
I.click(this.searchButton);
}
}
exportdefaultnewSearchPage();
Inject the page object into a specific scenario:
import searchPage from'./pages/searchPage.js'
Scenario('user searches for products',({I, searchPage })=>{
I.amOnPage('/');
searchPage.search('laptop');
I.see('Search Results');
}).injectDependencies({ searchPage });
Use cases:
Page objects needed only in a few tests
Test-specific configurations or data objects
Experimenting with new page objects before adding them globally
Note: For page objects used across multiple tests, add them to the include section in codecept.conf.js instead.
Class-based page objects are recommended for new code as they support lifecycle hooks and inheritance.
You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.