Mở đầu — vì sao bài này quan trọng
Trong hầu hết các dự án web, bạn sẽ gặp hai loại kiểm thử tự động sống song song: kiểm thử API (bằng Postman/Newman) và kiểm thử giao diện end-to-end (bằng Cypress). Nhiều đội xem chúng như hai thế giới tách biệt — team API test viết collection Postman, team frontend viết spec Cypress, và hai bên gần như không nói chuyện với nhau. Đó là một sự lãng phí lớn.
Thực tế, hai công cụ này bổ trợ cho nhau một cách hoàn hảo. Cypress rất mạnh trong việc mô phỏng hành vi người dùng thật trên trình duyệt — click nút, điền form, kiểm tra text hiển thị. Nhưng Cypress lại rất tệ (và chậm) khi bạn dùng nó để dựng dữ liệu nền: đăng ký 50 tài khoản, tạo 200 đơn hàng, hay reset trạng thái database. Ngược lại, Postman/Newman làm việc với API cực nhanh và ổn định, nhưng không thấy được giao diện người dùng.
Bài học này dạy bạn cách ghép hai công cụ trong cùng một pipeline: dùng sức mạnh API của Postman để chuẩn bị và dọn dẹp dữ liệu, dành Cypress cho đúng việc nó giỏi nhất là kiểm thử luồng UI. Đây là kỹ năng phân biệt một QA "biết chạy tool" với một QA thiết kế được chiến lược kiểm thử cho cả hệ thống. Với vai trò API Tester đang tiến tới SDET, đây chính là loại tư duy tích hợp mà nhà tuyển dụng tìm kiếm.
Khái niệm cốt lõi
Vì sao không dùng Cypress cho mọi thứ?
Cypress có thể gọi API qua cy.request(), nên về lý thuyết bạn có thể làm mọi thứ trong Cypress. Nhưng có ba lý do khiến việc tách vai trò là hợp lý:
- Tốc độ và sự cô lập. Dựng dữ liệu qua UI (đăng ký tài khoản bằng cách điền form) chậm gấp 10–20 lần so với gọi thẳng API. Một luồng chuẩn bị 100 sản phẩm qua UI có thể mất 5 phút; qua API chỉ vài giây.
- Tái sử dụng. Collection Postman đã có sẵn (từ team API test) chứa toàn bộ logic tạo dữ liệu, xác thực token, dọn dẹp. Thay vì viết lại bằng JavaScript trong Cypress, bạn tái dùng chính collection đó qua Newman.
- Trách nhiệm rõ ràng. Khi test fail, bạn biết ngay lỗi ở tầng nào: nếu bước Newman fail thì API hỏng; nếu bước Cypress fail mà API pass thì lỗi nằm ở frontend.
Ba mô hình tích hợp phổ biến
Mô hình 1 — Newman làm "setup/teardown" bao quanh Cypress. Đây là mô hình phổ biến nhất. Trong pipeline, bạn chạy Newman trước để seed dữ liệu (tạo user, tạo đơn hàng ở trạng thái cần thiết), sau đó chạy Cypress để test UI, cuối cùng chạy Newman lần nữa để dọn dẹp. Cypress chỉ tập trung vào luồng người dùng, không phải lo về dữ liệu.
Mô hình 2 — Cypress gọi Postman collection qua Newman Library ngay trong test. Newman có thể được nhúng như một thư viện Node.js (require('newman')). Kết hợp với cy.task() của Cypress, bạn có thể trigger một collection Postman ngay giữa một spec Cypress. Ví dụ: giữa test đăng nhập UI, bạn cần lấy OTP từ API nội bộ — gọi Newman để làm việc đó.
Mô hình 3 — Chia sẻ dữ liệu qua file trung gian. Newman xuất kết quả (token, ID vừa tạo) ra một file JSON; Cypress đọc file đó qua cy.readFile() để dùng lại. Đơn giản, không cần code phức tạp, rất hợp cho CI.
Điểm mấu chốt cần nhớ: Postman/Newman lo phần dữ liệu và trạng thái backend; Cypress lo phần trải nghiệm người dùng. Ranh giới này giữ cho bộ test của bạn nhanh, ổn định và dễ bảo trì.
Tình huống thực tế
Ví dụ 1 — Tiki: seed dữ liệu giỏ hàng trước khi test luồng thanh toán
Một đội QA tại một sàn thương mại điện tử lớn như Tiki cần kiểm thử luồng thanh toán trên UI. Kịch bản yêu cầu: người dùng đã đăng nhập, giỏ hàng có sẵn 3 sản phẩm với tổng giá trị trên 500.000đ để kích hoạt mã giảm giá freeship.
Ban đầu, họ viết toàn bộ trong Cypress: đăng nhập qua form, tìm 3 sản phẩm, thêm từng cái vào giỏ. Mỗi lần chạy mất khoảng 40 giây chỉ cho phần chuẩn bị, và thường xuyên flaky vì trang tìm kiếm load chậm. Với 20 test case thanh toán, phần setup ngốn hơn 13 phút và fail ngẫu nhiên 2–3 lần mỗi lượt CI.
Giải pháp: họ dùng collection Postman có sẵn của team API — gồm request POST /auth/login, POST /cart/items (thêm sản phẩm theo SKU). Trong pipeline, Newman chạy trước, đăng nhập và bơm thẳng 3 SKU vào giỏ qua API, rồi ghi cart_id và access_token ra file seeded-data.json. Cypress đọc file này, set cookie token và điều hướng thẳng tới trang /checkout.
Kết quả: phần setup từ 40 giây xuống còn 3 giây mỗi test, tỷ lệ flaky gần như biến mất. Bài học: đừng bắt Cypress làm việc backend giỏi hơn nhiều lần — hãy để API test lo phần dựng trạng thái.
Ví dụ 2 — Momo (giả định): lấy mã OTP qua API giữa luồng đăng ký UI
Một fintech kiểu Momo có luồng đăng ký yêu cầu nhập OTP gửi qua SMS. Trong môi trường staging, OTP không thực sự gửi SMS mà lưu vào một endpoint nội bộ GET /internal/otp/{phone} (chỉ bật ở staging). QA cần test luồng đăng ký UI đầy đủ, kể cả bước nhập OTP.
Cypress một mình không có cách "hợp lý" để lấy OTP — viết cy.request() trực tiếp thì được, nhưng logic xác thực header nội bộ khá phức tạp và đã nằm sẵn trong collection Postman. Đội dùng Mô hình 2: nhúng Newman qua cy.task(). Khi Cypress đến bước nhập OTP, nó gọi cy.task('runNewmanGetOtp', { phone }); task này chạy một collection Postman nhỏ, trả về mã OTP 6 số. Cypress điền OTP đó vào form và tiếp tục.
Điểm hay: logic gọi API nội bộ chỉ được viết một lần trong Postman, dùng chung cho cả API test và Cypress. Khi header xác thực nội bộ đổi, chỉ cần sửa ở collection. Bài học: khi một hành động API đã được "đóng gói" tốt trong Postman, hãy tái dùng thay vì viết lại trong Cypress.
Ví dụ 3 — Startup SaaS: dọn dẹp dữ liệu để test chạy lặp lại được
Một startup SaaS quản lý nhân sự chạy CI mỗi lần merge. Bộ Cypress test tạo ra hàng loạt "phòng ban" và "nhân viên" giả trong quá trình test UI. Sau vài tuần, database staging phình to hơn 40.000 bản ghi rác, khiến trang danh sách load chậm và một số test bắt đầu fail vì phân trang không như kỳ vọng.
Họ thêm một bước teardown bằng Newman ở cuối pipeline: một collection gọi DELETE /test-data?prefix=e2e- để xóa mọi bản ghi có tiền tố e2e- (mọi dữ liệu test đều được đặt tên với tiền tố này). Bước dọn dẹp chạy dù Cypress pass hay fail (dùng cờ always() trong CI). Sau khi áp dụng, database staging luôn sạch, và thời gian load trang trở lại ổn định.
Bài học: tích hợp Postman không chỉ để chuẩn bị dữ liệu mà còn để dọn dẹp — điều Cypress làm rất vụng về.
Hướng dẫn từng bước
Ta triển khai Mô hình 1 (Newman bao quanh Cypress) — mô hình dùng được cho hầu hết dự án.
Bước 1 — Chuẩn bị collection Postman. Xuất hai collection: seed.postman_collection.json (tạo dữ liệu, ghi kết quả vào biến environment) và cleanup.postman_collection.json (xóa dữ liệu test). Trong tab Tests của request seed, dùng pm.environment.set để lưu ID/token vừa tạo:
// Trong request "Create user" của seed collection
const json = pm.response.json();
pm.environment.set("access_token", json.token);
pm.environment.set("user_id", json.id);
pm.test("User created", () => pm.response.to.have.status(201));
Bước 2 — Cho Newman xuất kết quả ra file để Cypress đọc. Dùng reporter json hoặc dùng option --export-environment để lưu environment (chứa token, ID) sau khi chạy xong:
newman run seed.postman_collection.json \
-e staging.postman_environment.json \
--export-environment seeded-env.json
Bước 3 — Viết script kết nối. Trích các giá trị cần thiết ra một file phẳng cho Cypress dễ đọc. Có thể làm bằng một script Node nhỏ đọc seeded-env.json và ghi ra cypress/fixtures/seeded.json:
// scripts/extract-seed.js
const env = require('../seeded-env.json');
const get = (k) => env.values.find(v => v.key === k)?.value;
require('fs').writeFileSync(
'cypress/fixtures/seeded.json',
JSON.stringify({ token: get('access_token'), userId: get('user_id') }, null, 2)
);
Bước 4 — Dùng dữ liệu trong Cypress. Trong spec, đọc fixture và set token để bỏ qua bước đăng nhập UI:
// cypress/e2e/checkout.cy.js
describe('Checkout flow', () => {
before(() => {
cy.readFile('cypress/fixtures/seeded.json').then((d) => {
cy.setCookie('access_token', d.token);
Cypress.env('userId', d.userId);
});
}); it('hoàn tất thanh toán với giỏ hàng đã seed', () => {
cy.visit('/checkout');
cy.contains('Thanh toán').click();
cy.contains('Đặt hàng thành công').should('be.visible');
});
});
Bước 5 — Ghép vào pipeline CI (ví dụ GitHub Actions). Chạy tuần tự: seed → cypress → cleanup, với cleanup luôn chạy:
- name: Seed data
run: |
newman run seed.postman_collection.json -e staging.postman_environment.json --export-environment seeded-env.json
node scripts/extract-seed.js
- name: Run Cypress
run: npx cypress run
- name: Cleanup (always)
if: always()
run: newman run cleanup.postman_collection.json -e seeded-env.json
Bước 6 (tùy chọn) — Mô hình 2 với cy.task. Nếu cần gọi Postman giữa test, khai báo task trong cypress.config.js:
const newman = require('newman');
// setupNodeEvents:
on('task', {
runNewman({ collection, env }) {
return new Promise((resolve, reject) => {
newman.run({ collection, environment: env, reporters: [] },
(err, summary) => err ? reject(err) : resolve(extractOtp(summary)));
});
}
});
Sau đó trong test gọi cy.task('runNewman', {...}) để nhận kết quả.
Lỗi thường gặp & mẹo
- Lỗi để Cypress làm mọi việc backend "cho tiện". Rất hấp dẫn khi viết vài
cy.request()thay vì dựng Newman, nhưng khi số lượng lớn, bạn sẽ trùng lặp logic đã có trong Postman và khó bảo trì. Nguyên tắc: nếu logic API đã tồn tại trong collection, hãy tái dùng.
- Quên chạy teardown khi test fail. Nếu bước cleanup chỉ chạy khi Cypress pass, database sẽ ngập rác sau mỗi lần fail. Luôn dùng
if: always()(GitHub Actions) hoặcalways()/postblock tương ứng của CI khác.
- Đặt tên dữ liệu test không có tiền tố nhận diện. Không có tiền tố như
e2e-haytest-, collection cleanup không biết xóa gì và có nguy cơ xóa nhầm dữ liệu thật. Luôn gắn nhãn dữ liệu test ngay từ khâu seed.
- Chạy seed và Cypress song song. Đây là lỗi kinh điển: nếu Cypress khởi động trước khi Newman seed xong, test sẽ fail vì dữ liệu chưa có. Trong pipeline phải đảm bảo tuần tự (seed hoàn tất mới tới Cypress).
- Hardcode token vào Cypress. Token có thời hạn; hardcode sẽ khiến test fail sau vài giờ. Luôn lấy token động từ bước seed qua file trung gian.
- Mẹo — tách environment rõ ràng. Dùng cùng một file environment Postman (
staging.postman_environment.json) cho cả seed lẫn cleanup, và cùngbaseUrlvới Cypress, để tránh test chạy nhầm môi trường.
- Mẹo — giữ collection nhỏ và có mục đích đơn. Đừng nhồi cả bộ API regression vào collection seed. Collection dùng cho tích hợp Cypress nên gọn: chỉ những request cần cho việc dựng/dọn dữ liệu.
Bài tập thực hành
- Dựng luồng seed cơ bản. Tạo một collection Postman gồm hai request: đăng nhập (
POST /login) lưu token, và tạo một tài nguyên (ví dụPOST /todos) lưutodo_id. Chạy bằng Newman với--export-environmentvà xác nhận file environment xuất ra chứatokenvàtodo_id.
- Kết nối sang Cypress. Viết script
extract-seed.jsđể tríchtokenvàtodo_idracypress/fixtures/seeded.json. Viết một spec Cypress đọc fixture, set cookie token,cy.visit('/todos')và kiểm tra todo vừa tạo hiển thị.
- Thêm teardown an toàn. Tạo collection cleanup xóa todo theo
todo_id. Ghép cả ba bước (seed → cypress → cleanup) vào một script npm hoặc workflow CI, đảm bảo cleanup chạy kể cả khi Cypress fail (thử cố tình làm một assertion fail để kiểm chứng).
- (Nâng cao) Thử Mô hình 2. Khai báo một
cy.tasknhúng Newman, gọi một collection nhỏ trả về một giá trị (ví dụ mã xác nhận giả), và dùng giá trị đó điền vào form trong spec Cypress.
Tóm tắt
Postman và Cypress không phải hai công cụ cạnh tranh mà là hai mảnh ghép của một chiến lược kiểm thử hoàn chỉnh: Postman/Newman làm chủ tầng dữ liệu và trạng thái backend, còn Cypress đảm nhận trải nghiệm người dùng trên UI. Ba mô hình tích hợp — Newman bao quanh Cypress để seed/teardown, nhúng Newman qua cy.task để gọi API giữa test, và chia sẻ dữ liệu qua file trung gian — giúp bạn ghép chúng linh hoạt tùy nhu cầu. Mô hình phổ biến và bền vững nhất là để Newman seed dữ liệu trước, xuất token/ID ra file, Cypress đọc file đó rồi test UI, và cuối cùng Newman dọn dẹp với always(). Nắm được cách phân vai này, bạn không chỉ làm test nhanh và ổn định hơn, mà còn thể hiện tư duy kiến trúc kiểm thử ở tầm SDET — đúng hướng nghề nghiệp mà khóa học đang dẫn dắt.