git clone <repository-url>
npm install
npx playwright install
.env
file (based on .env.example
).npm run test
npm run test:ui
npm run test:report
npm run test:codegen
Tests follow the BDD and Page Object Model (POM) patterns with structured test data.
camelCase
.CapitalCase
.src/features
.src/features/steps
.Feature files should be placed in src/features
and have a .feature
extension.
Use the Given, When, Then
format to define steps:
Feature: Feature you want to test
Rule: Describe the user story or functionality
Scenario: I can/should... (the expected behavior)
Given I <initial setup>
When I <action>
Then I should <expected outcome>
And
for subsequent steps instead of repeating Given
, When
, or Then
.Wrong Example:
Scenario: I should log in successfully
Given I navigate to the Website
When I enter credentials
When I click on the sign-in button
Then I should see the title after login
Correct Example:
Scenario: I should log in successfully
Given I navigate to the Website
When I enter credentials
And I click on the sign-in button
Then I should see the title after login
Background
and Scenario Outline
for reusable steps and reduce code duplication.Correct Data Table Example:
Scenario: Log in as a newly registered user with valid data
When I enter valid credentials
| email | password | title |
| user1@example.com | pass123 | Home |
| user2@example.com | pass123 | Home |
And I click the sign-in button
Then I should see the homepage
Use tags with Feature, Rule, or Scenario:
If you need to create a new tag for the new user type. Example: To create a new tag @gdvphn1, add a new object to the file src/data/users.data.ts
export const authGDVPVFile = path.join(__dirname, "../.auth/GDVPHN1.json");
export const users = {
gdvphn1: {
username: "*****@ncc.asia",
password: "*****",
name: "*****",
authFile: authGDVPDNFile,
},
};
Avoid combining multiple actions into one step. This will improve reusability and readability.
Wrong Example:
Given the user is logged in and on the dashboard and has notifications
Correct Example:
Given the user is logged in
And the user is on the dashboard
And the user has notifications
Focus on what happens, not how it happens.
Wrong Example (Imperative):
Scenario: I should log in successfully
Given I navigate to the Website
When I enter “username”
And I enter “password”
And I check the “Remember me” check box
And I click on the sign-in button
Then I should see the title after login
Correct Example (Declarative):
Scenario: I should log in successfully
Given I navigate to the Website
When I enter credentials
And I click the sign-in button
Then I should see the title after login
Step definition files are located in src/features/steps
. Each step file should map to a specific page. Common steps should be placed in share.step.ts
.
src/pageObjects/pages
.src/pageObjects/components
.To create a new Page Object:
src/pageObjects/pages
with the format <name>.page.ts
.page.fixture.ts
.Example:
export type PageObjects = {
LoginPage: LoginPage;
MyRequestPage: MyRequestPage;
TaskPage: TaskPage;
};
const convertToPageObjects = (page: Page): PageObjects => ({
LoginPage: new LoginPage(page),
MyRequestPage: new MyRequestPage(page),
TaskPage: new TaskPage(page),
});
BasePage
.Example:
export default class RequestTemplatePage extends BasePage { // should extends base page
public table: Table; // component in page, It's custom components in ./components folder
public form: TemplateForm; // component in page, It's custom components in ./components folder
private get createBtn() { // element in page
return this.page.getByRole("button", { name: "Create" }); // use playwright locator
}
constructor(readonly page: Page) {
super(page, "/my-requests"); // set path of page
this.table = new Table(page, this.page.getByTestId("request-table")); // new component you want to set
this.form = new TemplateForm(page, this.page.getByTestId("request-form")); // new component you want to set
}
async createNewTemplate(data: TemplateForm) { // user action - create new template
await this.createBtn.click(); // click create btn
await this.form.fill(data); // fill form
await this.form.submit(); // click submit
}
async verifyTemplateByTitle(title: string) { // verify method to check expectation
await this.table.verifyTextInCol(0, title)
}
}
waitLoading
: Wait for skeleton loading to complete.BrowserControl
: Supports opening a new browser session with user authentication.
// authAdminFile ==> is the path to auth user (setup in users.data.ts)
await BrowserControl.withAuth(browser, authAdminFile, async ({ PageObjects }) => {
// in this callback. You can action as Admin user
await PageObjects.RequestTemplatePage.open();
await PageObjects.RequestTemplatePage.verifyPageLocated();
// create a new template (only admin have permission)
await PageObjects.RequestTemplatePage.createNewTemplate(templateData);
});
src/data
src/data/features.data.ts
*testData[<property or method of testData object>].<next property or method>
Example:
Feature: As user, I want to login to w2
Scenario: Login success
Given I am on "LoginPage"
When I login with username "*testData[users].user.username" and password "*testData[users].user.password"
Then I should see "RequestTemplatePage"
*testData[<method of testData object>]__global[<key in global object>]
*global[<key in global object>].<method or property>
Example:
Feature: Device Request
Rule: As pm, I want to received a Device Request from my project
Background:
Given User create "Device Request" with "*testData[random_device_request]__global[drkey1]" success
And I am on "TaskPage"
Scenario: I should see the request with pending status on my tasks
Then I should see request is "pending" with title "*global[drkey1].getTitle" and state "PM Reviews" on tasks page