.feature (Gherkin)
↓
Step Definitions (Cucumber)
↓
Playwright API (browser automation)
↓
Browser (Chromium/WebKit/Firefox)
↓
App (Angular / API)
👉 Cucumber = viết kịch bản (readable specs)
👉 Playwright = thực thi automation (powerful browser control)
npm init -y
npm install -D \
@cucumber/cucumber \
playwright \
ts-node \
typescript \
@types/node
project/
│
├── features/
│ └── login.feature
│
├── steps/
│ └── login.steps.ts
│
├── support/
│ ├── hooks.ts
│ └── world.ts
│
├── pages/
│ └── login.page.ts
│
├── cucumber.js
└── tsconfig.json
👉 Structure này giúp scale và maintain dễ dàng trong project thật.
Feature: Login
Scenario: Login thành công
Given user mở trang login
When user nhập username "admin" và password "123456"
Then user thấy dashboard
import { Given, When, Then } from "@cucumber/cucumber";
import { chromium, Browser, Page } from "playwright";
import assert from "assert";
let browser: Browser;
let page: Page;
Given("user mở trang login", async () => {
browser = await chromium.launch({ headless: false });
page = await browser.newPage();
await page.goto("http://localhost:4200/login");
});
When(
"user nhập username {string} và password {string}",
async (username, password) => {
await page.fill("#username", username);
await page.fill("#password", password);
await page.click("#login-btn");
},
);
Then("user thấy dashboard", async () => {
await page.waitForSelector("#dashboard");
const text = await page.textContent("#dashboard");
assert(text?.includes("Welcome"));
await browser.close();
});
👉 Không nên viết selector trực tiếp trong steps
pages/login.page.tsimport { Page } from "playwright";
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto("/login");
}
async login(username: string, password: string) {
await this.page.fill("#username", username);
await this.page.fill("#password", password);
await this.page.click("#login-btn");
}
}
import { LoginPage } from "../pages/login.page";
let loginPage: LoginPage;
Given("user mở trang login", async function () {
this.browser = await chromium.launch();
this.page = await this.browser.newPage();
loginPage = new LoginPage(this.page);
await loginPage.goto();
});
When(
"user nhập username {string} và password {string}",
async function (username, password) {
await loginPage.login(username, password);
},
);
support/hooks.tsimport { Before, After } from "@cucumber/cucumber";
import { chromium } from "playwright";
Before(async function () {
this.browser = await chromium.launch();
this.page = await this.browser.newPage();
});
After(async function () {
await this.browser.close();
});
👉 Lợi ích:
support/world.tsimport { setWorldConstructor } from "@cucumber/cucumber";
import { Browser, Page } from "playwright";
class CustomWorld {
browser!: Browser;
page!: Page;
}
setWorldConstructor(CustomWorld);
cucumber.jsmodule.exports = {
default: {
require: ["steps/**/*.ts", "support/**/*.ts"],
requireModule: ["ts-node/register"],
format: ["progress"],
},
};
npx cucumber-js
Business viết Gherkin
↓
Dev map step → code
↓
Playwright điều khiển browser
↓
Test UI / API thật
↓
Generate report
| ❌ Sai | ✅ Đúng |
|---|---|
| Viết selector trực tiếp trong step | Dùng Page Object Pattern |
| Không dùng Page Object | Tách riêng page classes |
| Test phụ thuộc nhau (state leak) | Mỗi test độc lập |
| Không handle async properly | Dùng async/await đúng cách |
| Selector yếu (brittle tests) | Dùng data-testid hoặc stable selectors |
Scenario Outline: Login với nhiều data sets
When user login với "<username>" và "<password>"
Then thấy "<result>"
Examples:
| username | password | result |
| admin | 123456 | success |
| admin | wrong | error |
| invalid | 123456 | error |
→ Chạy nhiều browser cùng lúc để tăng tốc độ
→ Login bằng API → Skip UI login → Test nhanh hơn nhiều lần
→ Cực kỳ quan trọng trong project lớn với unstable network/services
Cucumber = viết spec readable cho cả team (BA, QA, Dev)
Playwright = thực thi automation mạnh mẽ, cross-browser
Kết hợp cả hai giúp bạn có: