NỘI DUNG BÀI HỌC

🚀 Bẻ khóa Playwright: Hiểu sâu bản chất Fixture không phải phép thuật, mà là Dependency Injection (DI).
🤖 Phương pháp trực quan: Học qua mô hình "Robot Đầu Bếp",
🛠️ Thực chiến Deep Dive: Tự tay code lại Core Container để thấu hiểu từng luồng chạy ngầm bên dưới.
📌 Kỹ thuật Fixture căn bản: Cách khởi tạo, Merge và Inject đa dạng kiểu dữ liệu (Primitive, Object) vào Test Case.

🍳 Phần 1: Tại sao phải đi "nhà hàng"? (Khái niệm DI)

"Chào các bạn, trước khi nói về những dòng code phức tạp, hãy tưởng tượng trưa nay các bạn muốn ăn món Trứng Rán. Chúng ta có hai cách để làm việc này."


Cách thứ nhất: Ăn cơm nhà (Non-DI / Hard-coding)

"Nếu tự nấu ở nhà, quy trình sẽ như thế này:

  • B1: Các bạn phải tự chạy ra chợ mua trứng.

  • B2: Tự rửa cái chảo.

  • B3: Tự bật bếp ga.

  • B4: Cuối cùng mới rán được trứng.

    👉 Vấn đề: Rất mệt! Các bạn chỉ muốn ăn thôi, tại sao phải quan tâm đến việc mua trứng hay rửa chảo? Trong lập trình, đây là khi các bạn viết new Page(), new Login() lặp đi lặp lại. Code dính chặt vào nhau, rất khó sửa."


Cách thứ hai: Ăn nhà hàng 5 sao (DI Pattern)

"Bây giờ, hãy tưởng tượng chúng ta vào một nhà hàng có một con Robot Đầu Bếp siêu hạng.

    • B1: Các bạn ngồi vào bàn.

    • B2: Hô to: 'Cho một Trứng Rán!' (Đây là lúc viết hàm test).

    • B3: Con Robot tự động chạy đi làm mọi thứ và bưng món ăn ra tận bàn.

      👉 Lợi ích: Các bạn rảnh tay hoàn toàn. Trong lập trình, mô hình 'chỉ việc gọi món' này gọi là Dependency Injection (DI)."

 

🤖 Phần 2: Giải phẫu "Bộ não" Robot (Cơ chế hoạt động)

 

"Nhưng khoan đã! Con Robot này không phải là phù thủy. Làm sao khi ta hô 'Bánh Mì Trứng', nó biết phải đi lấy cái chảo mà không phải cái búa? Bí mật nằm ở bộ não của nó với 2 ngăn chính."

1. Ngăn thứ nhất: Sổ tay công thức (Registry)

"Giống như việc dạy trẻ con, đầu tiên ta phải dạy Robot (đây là bước test.extend trong Playwright):

  • Bài học 1: Muốn có Trứng Rán 🍳, mày phải có Trứng Tươi + Chảo.

  • Bài học 2: Muốn có Bánh Mì Trứng 🥪, mày phải có Bánh Mì + Trứng Rán."

2. Ngăn thứ hai: Tư duy ngược (Resolution)

"Khi các bạn gọi món 'Bánh Mì Trứng', bộ não Robot sẽ chạy một quy trình suy luận cực hay gọi là Chain of Dependency (Chuỗi phụ thuộc):

    1. Robot nghe lệnh: 'Cần Bánh Mì Trứng'.

    2. Tra sổ tay: Thấy thiếu 'Trứng Rán'. 🛑 Dừng lại, đi làm trứng rán trước.

    3. Tra sổ tay tiếp: Thấy thiếu 'Chảo'. 🛑 Dừng lại, đi lấy chảo trước.

    4. Có Chảo -> Làm xong Trứng Rán -> Làm xong Bánh Mì Trứng.

      👉 Nó tự động lắp ráp từ những cái nhỏ nhất lên cái lớn nhất."



Phần 3: Xây dựng "Bộ não" từ cách đơn giản nhất

Chúng ta sẽ cùng nhau xây dựng "Bộ não" của con Robot này từ con số 0. Mục tiêu là để các bạn thấy rằng Playwright không có phép thuật gì cả, nó cũng chỉ chạy theo logic này thôi.

Để dễ hiểu, thầy trò mình sẽ chia code làm 3 phần:

  1. Nguyên liệu & Món ăn: Các vật dụng thông thường.

  2. Bộ não Robot (Quan trọng nhất): Nơi chứa logic DI Container.

  3. Kịch bản chạy: Cách ta dùng Robot.

Dưới đây là đoạn code mô phỏng. Các bạn hãy đọc kỹ phần chú thích nhé:

 
// --- PHẦN 1: CÁC NGUYÊN LIỆU VÀ MÓN ĂN (Dependency) ---

// 1. Cái chảo (Dụng cụ cơ bản nhất, không cần gì khác)
class CaiChao {
  lamNong() {
    console.log("🔥 Đang bật bếp làm nóng chảo...");
    return "Chảo nóng";
  }
}

// 2. Trứng rán (Món sơ chế)
class TrungRan {
  constructor(chao) {
    // Để rán trứng, bắt buộc phải có cái chảo
    this.chao = chao;
  }

  ran() {
    this.chao.lamNong();
    console.log("🍳 Đập trứng vào chảo... Xèo xèo... Chín!");
    return "Miếng trứng vàng ươm";
  }
}

// 3. Bánh mì trứng (Thành phẩm cuối cùng)
class BanhMiTrung {
  constructor(trungRan) {
    // Để làm bánh mì, bắt buộc phải có trứng đã rán
    this.trung = trungRan;
  }

  an() {
    const nhanBanh = this.trung.ran();
    console.log(`🥪 Kẹp ${nhanBanh} vào bánh mì. Mời sếp ăn!`);
  }
}

// --- PHẦN 2: BỘ NÃO ROBOT (DI CONTAINER) ---
// Đây là trái tim của hệ thống, tương tự như Playwright Runner

class RobotDauBep {
  constructor() {
    // Cuốn sổ tay ghi công thức nấu ăn (Map lưu trữ)
    this.soTayCongThuc = new Map();
  }

  // Hành động 1: HỌC CÔNG THỨC (Register)
  // Tương đương: test.extend(...) trong Playwright
  hocCongThuc(tenMon, cachLam) {
    console.log(`✅ Đã học cách làm: ${tenMon}`);
    this.soTayCongThuc.set(tenMon, cachLam);
  }

  // Hành động 2: NẤU MÓN (Resolve)
  // Tương đương: Khi Playwright khởi tạo biến trong hàm test({ ... })
  nauMon(tenMon) {
    console.log(`🤖 Sếp gọi món '${tenMon}'. Robot đang suy nghĩ...`);
    
    // 1. Tìm công thức trong sổ
    const hamLamMon = this.soTayCongThuc.get(tenMon);
    
    if (!hamLamMon) {
      throw new Error(`❌ Chịu! Món '${tenMon}' này chưa được dạy!`);
    }

    // 2. Thực hiện làm món
    // Truyền chính bản thân con Robot (this) vào 
    // để nếu món đó cần nguyên liệu khác, nó lại nhờ Robot lấy tiếp (Đệ quy)
    return hamLamMon(this);
  }
}

// --- PHẦN 3: KỊCH BẢN SỬ DỤNG THỰC TẾ ---

// B1: Mua Robot về
const robot = new RobotDauBep();

// B2: Dạy Robot (Cấu hình Fixture)
// Dạy làm Chảo
robot.hocCongThuc("CaiChao", (r) => new CaiChao());

// Dạy làm Trứng Rán (Cần Chảo -> Gọi r.nauMon lấy Chảo)
robot.hocCongThuc("TrungRan", (r) => {
  const chao = r.nauMon("CaiChao"); 
  return new TrungRan(chao);
});

// Dạy làm Bánh Mì (Cần Trứng Rán -> Gọi r.nauMon lấy Trứng)
robot.hocCongThuc("BanhMiTrung", (r) => {
  const trung = r.nauMon("TrungRan");
  return new BanhMiTrung(trung);
});

// B3: Khách gọi món (Test Case)
console.log("\n--- BẮT ĐẦU PHỤC VỤ ---");
const monAn = robot.nauMon("BanhMiTrung"); // Khách chỉ cần gọi món cuối cùng
monAn.an();

🔑 Chìa khóa nằm ở dòng này:

Trong phần dạy công thức TrungRan:

robot.hocCongThuc("TrungRan", (r) => {

  // 👇 CHÍNH LÀ DÒNG NÀY!

  const chao = r.nauMon("CaiChao");




  return new TrungRan(chao);

});

Và sự hỗ trợ đắc lực từ bên trong hàm nauMon:

// Trong class RobotDauBep

nauMon(tenMon) {

  // ... tìm công thức ...

  // 👇 TRUYỀN CHÍNH NÓ (this) VÀO CÔNG THỨC

  return hamLamMon(this);

}

💡 Phân tích

Nhìn kỹ vào biến r trong dòng (r) => ....

  1. r là ai?

r chính là con Robot (Container) đang chạy.

  • Khi Robot bắt đầu làm món, nó tự truyền chính bản thân nó (this) vào trong công thức.
  • Điều này giống như Robot nói với công thức: "Này, tao đang thực hiện công thức đây. Nếu mày cần thêm thứ gì khác, cứ sai bảo tao đi!"
  1. Điều gì xảy ra tại dòng r.nauMon("CaiChao")?

Đây là hành động "Nhờ vả ngược lại" (Recursive Call).

  • Thay vì tự new CaiChao() (tự đi mua chảo), công thức lại ra lệnh cho Robot: "Ê Robot (r), đi lấy cho tao cái Chảo (nauMon("CaiChao")) về đây!"
  1. Tại sao nó "thần thánh"?

Bởi vì Robot rất thông minh (như ta đã lập trình ở hàm nauMon).

  • Khi nghe lệnh lấy "Chảo", Robot sẽ tạm dừng việc làm Trứng.
  • Nó chạy đi tìm công thức "CaiChao".
  • Nó tạo ra cái chảo.
  • Nó mang cái chảo về lại cho dòng code đang chờ.

👉 Kết luận: Chính việc gọi lại r.nauMon(...) bên trong một công thức khác đã tạo nên chuỗi liên kết tự động.

🖼️ Minh họa bằng hội thoại vui

Khách: Cho 1 Trứng Rán!

Robot (Bắt đầu chạy công thức Trứng Rán): Ok, để xem... món này cần Chảo.

Công thức Trứng Rán: Này Robot (r), tao không biết làm chảo. Mày (r) hãy tự dùng trí tuệ của mày (nauMon) để đi kiếm cái Chảo về đây cho tao!

Robot: Ok, chờ tí... (Robot tự chạy đi kiếm chảo)... Xong! Đây là cái chảo.

Công thức Trứng Rán: Tốt, giờ tao đập trứng vào chảo này. Xong món!


Hãy mang con Robot đó vào code thật của Playwright nhé.

Đây là cú pháp cơ bản nhất mà bạn sẽ gặp hàng ngày. Hãy xem sự tương đồng đến kinh ngạc giữa lý thuyết "Robot" và thực tế:

import { test } from '@playwright/test'; // 1. Gọi con Robot ra

// 2. Ra lệnh (Viết Test)
test('Tôi muốn vào Google', async ({ page }) => { // 3. Gọi món ({ page })
  
  // 4. Robot đã dâng "page" tận miệng, giờ bạn chỉ việc dùng
  await page.goto('https://google.com');
  
});

Chúng ta hãy "mổ xẻ" 3 thành phần chính khớp với ví dụ Robot lúc nãy:

  1. import { test }: Đây chính là Con Robot (DI Container). Nó chứa sẵn các công thức cơ bản.

  2. async ({ page }): Đây là bước Gọi món (Request/Resolve).

    • Bạn không hề viết const page = new Page().

    • Bạn chỉ để chữ page vào trong ngoặc nhọn {}.

    • Playwright (Robot) nhìn thấy chữ page, nó tự tra từ điển, tự bật trình duyệt, tự tạo tab mới và "tiêm" (inject) vào biến page cho bạn dùng.

  3. Hàm bên trong { ... }: Đây là lúc bạn Ăn (Tiêu thụ/Consume). Bạn dùng cái page đó để đi tới Google.

 

🌉 Phần 4: Cầu nối từ Lý thuyết DI đến Thực tế Playwright

 Đặt vấn đề: Ai là "Người Phục Vụ"?

"Mọi người đã hiểu mô hình 'Nhà hàng' (DI) rồi: Khách hàng chỉ việc gọi món, còn việc làm ra món đó là việc của nhà bếp. Nhưng trong code Playwright, ai đóng vai trò gì?"

Khách hàng (Consumer): Chính là Hàm Test (test('...', ...)).

Món ăn (Dependency): Chính là các đối tượng như page, request, context.

Người Phục vụ & Nhà bếp (Injector & Container): Đây là câu hỏi lớn. Ai là người lẳng lặng tạo ra page và đưa nó vào hàm test?

👉 Câu trả lời: Đó chính là Playwright Test Runner.


Định nghĩa lại Fixture dưới góc nhìn DI

"Vậy Fixture là gì trong hệ thống này?"

Chúng ta hãy định nghĩa lại nó một cách kỹ thuật nhưng dễ hiểu:

Fixture chính là một 'Dịch vụ' (Service) hoặc một 'Món ăn' đã được đăng ký vào trong hệ thống DI của Playwright.

Khi dùng lệnh test.extend(...), thực chất  đang làm hành động Đăng ký (Registration):

"Này Playwright (Container), tôi muốn thêm một món mới vào thực đơn."

"Món này tên là 'todoPage'."

"Cách chế biến món này là: Bật browser lên, đăng nhập, rồi trả về trang Todo."


Phân tích cú pháp: "Giải mã" dòng code test

Hãy nhìn vào dòng code quen thuộc này dưới lăng kính của DI:

test('Tên bài test', async ({ page, todoPage }) => {

    // Logic test...

});

Chúng ta sẽ "mổ xẻ" nó ra:

test(...): Đây là lúc gọi "Người quản lý" (Test Runner) ra làm việc.

({ page, todoPage }): Tại sao lại để trong dấu ngoặc nhọn {}?

Trong JavaScript, đây là Object Destructuring.

Nhưng trong tư duy DI của Playwright, đây chính là Danh sách yêu cầu (Dependencies Declaration).

Dòng này có nghĩa là: "Này Playwright, để chạy bài test này, tôi CẦN 2 thứ: một cái page sạch và một cái todoPage đã setup sẵn. Hãy đưa (inject) chúng cho tôi!"

Playwright làm gì ngầm bên dưới?

Nó nhìn vào danh sách yêu cầu: page, todoPage.

Nó lục lại "Sổ tay" (nơi đã test.extend).

Nó chạy code trong Fixture để tạo ra todoPage.

Nó "tiêm" (inject) kết quả vào hàm test để dùng.


Tóm tắt sự chuyển đổi tư duy

Tư duy cũ (Thủ công)

Tư duy Playwright (Fixture/DI)

Tự làm: const page = await browser.newPage()

Yêu cầu: async ({ page }) => ...

Quản lý: Phải nhớ page.close() cuối bài.

Tự động: Fixture tự dọn dẹp (Teardown) khi xong.

Kết nối: Code setup dính liền code test.

Tách biệt: Setup nằm ở file Fixture, Test nằm ở file Spec.

 

🍳 Phần 5: Thực hành - Món khai vị đơn giản (Text Fixture)

"Để chứng minh cơ chế 'gọi món' này hoạt động thế nào, chúng ta sẽ không nấu món phức tạp như 'Trang Web' vội. Chúng ta sẽ dạy Robot làm một món tráng miệng đơn giản: Một lời chào."

Chúng ta sẽ thực hiện 3 bước:

  1. Nhập Robot gốc về.
  2. Dạy Robot công thức (Fixture).
  3. Gọi món để kiểm tra (Test).

Hãy xem đoạn code dưới đây:

// BƯỚC 1: NHẬP ROBOT GỐC

// Chúng ta đổi tên 'test' gốc thành 'base' (nghĩa là robot cơ bản)

// để dành cái tên 'test' cho con robot xịn hơn sắp tạo.

import { test as base } from '@playwright/test';


// BƯỚC 2: DẠY ROBOT (Định nghĩa Fixture)

// Chúng ta mở rộng (extend) bộ não của robot gốc

export const test = base.extend({


  // Tên món ăn: 'loiChao'

  // ({}, use): Món này siêu dễ, không cần nguyên liệu gì thêm (ngoặc rỗng {})

  loiChao: async ({}, use) => {

   

    // 1. Chế biến (Setup - Làm trong bếp)

    // Khách hàng không hề biết ta làm bước này

    const text = "Xin chào Sếp! Robot đã sẵn sàng phục vụ.";

   
    // 2. Dâng món (Handover - Bưng ra bàn)

    // Robot đưa 'text' cho khách và đứng chờ

    await use(text);

  },

});


// BƯỚC 3: KHÁCH GỌI MÓN (Sử dụng trong Test)

// Chú ý: Ta dùng 'test' mới vừa tạo ở trên, không phải 'test' của Playwright nữa

test('Kiểm tra lời chào', async ({ loiChao }) => {

  // Khách chỉ việc nhận hàng và dùng

  console.log(loiChao);

});

🔍 Phân tích sâu: Tại sao code lại viết như vậy?

Để các hiểu rõ từng ký tự, hãy nhìn bảng đối chiếu giữa CodeNhà Bếp Robot:

Code Playwright

Nhà Bếp Robot

Giải thích chi tiết

import { test as base }

Mua Robot cơ bản

Robot mặc định của nhà máy chỉ biết mở trình duyệt, chưa biết nói "Xin chào". Ta gọi nó là base (nền tảng) để chuẩn bị nâng cấp.

base.extend(...)

Mở Sổ Tay Công Thức

Lệnh extend (mở rộng) chính là lúc ta nạp thêm kiến thức vào bộ não Robot.

loiChao:

Tên Món trong Menu

Đây là cái tên mà sau này các em sẽ "hô" lên để gọi món.

async ({}, use)

Quy trình chế biến

* {}: Danh sách nguyên liệu đầu vào. Vì món này quá dễ, không cần gì cả nên để rỗng.



* use: Chính là người phục vụ bưng bê.

const text = "..."

Nấu ăn trong bếp kín

Đây là phần bí mật. Hàm test không hề biết dòng chữ này được tạo ra bằng cách gán biến, hay lấy từ database, hay tính toán. Nó bị ẩn đi (Encapsulation).

await use(text)

Dâng món lên bàn

Đây là khoảnh khắc quan trọng nhất! Robot trao giá trị text cho bài test.

Chúng ta vừa tạo xong một Fixture đơn giản nhất hệ mặt trời.

Tại sao lại cứ phải as base rồi lại export test?

Để giải thích dễ hiểu, chúng ta hãy quay lại ví dụ Nhà Máy Robot.

Tại sao phải đổi tên (import { test as base })?

Hãy tưởng tượng trong xưởng của bạn có một quy tắc: "Robot phục vụ chính thức luôn phải có tên bảng tên là test".

Vấn đề nảy sinh ở đây:

  1. Bạn nhập con Robot tiêu chuẩn từ nhà máy Playwright về. Tên mặc định của nó là test.
  2. Bạn muốn "độ" nó (thêm tính năng loiChao) và tạo ra một con Robot mới. Bạn cũng muốn đặt tên con mới này là test (để đúng quy tắc xưởng).

🚨 Xung đột: Bạn không thể có 2 con Robot cùng tên là test trong cùng một phòng (file) được!

👉 Giải pháp:

Khi nhập con Robot tiêu chuẩn về, bạn nhanh tay dán đè một cái nhãn mới lên trán nó là base (nghĩa là: Nền tảng/Cơ sở).

  • import { test as base }: "Cho tôi nhập con robot test, nhưng ở đây tôi sẽ gọi nó là base nhé."

Sự tiến hóa của các con Robot

Bây giờ trong xưởng của bạn đang diễn ra quá trình "tiến hóa" như sau:

Robot

Tên gọi trong Code

Kỹ năng (Features)

Robot Cũ (Tiêu chuẩn)

base

Chỉ biết mở trình duyệt (page), mở API (request).

Robot Mới (Đã độ)

test

Thừa hưởng tất cả kỹ năng của base (biết mở page) CỘNG THÊM kỹ năng mới (loiChao).


Quy trình lắp ráp (Code Flow)

Hãy nhìn lại đoạn code với tư duy "lắp ráp":

// BƯỚC 1: Nhập Robot cũ về, đổi tên thành 'base' để dành cái tên 'test' cho con mới

import { test as base } from '@playwright/test';



// BƯỚC 2: Tạo Robot mới dựa trên Robot cũ

// "Lấy con base, gắn thêm (extend) món 'loiChao', rồi đặt tên thành phẩm là 'test'"

export const test = base.extend({

  loiChao: async ({}, use) => { ... }

});


Tại sao khi Test lại dùng được?

Khi bạn viết file test khác và import { test } từ file fixture này:

import { test } from './my-fixture'; // Nhập con Robot Mới (đã độ)


test('Ví dụ', async ({ page, loiChao }) => {

    // Vì đây là Robot Mới, nó hiểu cả 2 lệnh:

    // 1. 'page': Kỹ năng cũ (thừa hưởng từ base)

    // 2. 'loiChao': Kỹ năng mới (vừa được độ thêm)

});

 

 

Giả sử trong test.extend(...), bạn muốn dùng lại fixture page (của con base) để tạo ra món loiChao (ví dụ: lấy tiêu đề trang web làm lời chào). Bạn có cần phải khai báo lại cách tạo page không, hay Robot mới tự động biết cách lấy nó?

Câu trả lời ngắn gọn là: KHÔNG! Bạn KHÔNG cần khai báo lại.

Con Robot mới (test) cực kỳ thông minh. Nó thừa hưởng (inherit) toàn bộ trí nhớ và kỹ năng của con Robot cũ (base).

Hãy cùng phân tích chi tiết xem tại sao nó làm được điều đó nhé.

Minh họa bằng Code

Đây là cách bạn viết code để loiChao lấy tiêu đề từ page.

import { test as base } from '@playwright/test';

export const test = base.extend({


  // Tên món: 'loiChao'

  // Nguyên liệu cần: 'page' (Của con Robot cũ)

  // 👇 Bạn chỉ cần ghi tên 'page' vào đây thôi!

  loiChao: async ({ page }, use) => {

    // 1. Chế biến

    // Robot tự động lấy kỹ năng 'page' cũ ra dùng

    await page.goto('https://playwright.dev');

    const title = await page.title(); // Lấy tiêu đề: "Fast and reliable..."

    // 2. Dâng món (Trả về cái tiêu đề đó làm lời chào)

    await use(`Xin chào! Bạn đang ở trang: ${title}`);

  },

});

Phân tích: Tại sao Robot "Tự Biết"?

Hãy tưởng tượng quy trình nâng cấp Robot diễn ra như sau:

  1. Robot Cũ (base): Trong bộ não nó đã có sẵn công thức làm món page, request, browser.
  2. Nâng cấp (extend): Khi bạn viết extend(...), bạn đang cài đặt một bản cập nhật phần mềm.
  3. Robot Mới (test): Nó giữ nguyên toàn bộ công thức cũ và cộng thêm công thức mới.

Quy trình chạy ngầm (Luồng tư duy của Robot):

Khi bạn chạy test và gọi món loiChao:

  1. Robot: "Chủ nhân gọi món loiChao."
  2. Robot (Kiểm tra công thức loiChao): "Để làm món này, mình cần nguyên liệu là page."
  3. Robot (Tự hỏi): "Mình có biết làm page không nhỉ?"
    • Nó lục trong phần mở rộng mới: Không thấy.
    • Nó lục trong bộ nhớ gốc (base): THẤY RỒI!
  4. Hành động:
    • Nó chạy công thức gốc để tạo ra page.
    • Nó ném cái page vừa tạo vào cho hàm loiChao.
    • Hàm loiChao chạy và trả về kết quả cuối cùng.
  1. Sức mạnh của sự "Kế thừa"

Đây chính là vẻ đẹp của kiến trúc Playwright. Bạn có thể tạo ra một chuỗi dây chuyền các Robot (Fixture) nối tiếp nhau mà không bao giờ phải viết lại code cũ.

Ví dụ về dây chuyền 3 đời Robot:

  1. Robot Gốc (base): Biết làm page.
  2. Robot Đời 2 (testWithUser): Kế thừa page -> Biết làm loggedInUser.
  3. Robot Đời 3 (testWithCart): Kế thừa loggedInUser -> Biết làm shoppingCart.
Chỉ cần nhớ quy tắc này: "Cái gì đời trước đã biết làm, đời sau cứ thế mà sai bảo, không cần dạy lại.

🤖 Phần 6: Nâng Cấp Robot Đa Năng (Multiple Fixtures)

"Ở bài trước, chúng ta đã dạy Robot nói 'Lời chào'. Nhưng một con Robot xịn không thể chỉ biết mỗi chào hỏi.

Hôm nay, chúng ta sẽ nâng cấp bộ não của nó để xử lý Combo 3 món ăn cùng một lúc:

Tính toán: Tự sinh ra số ngẫu nhiên (randomNumber).

Giao tiếp: Tự tạo lời chào (greeting).

Quản lý dữ liệu: Tự tạo hồ sơ người dùng (userInfo)."

Mục tiêu: Giúp các bạn hiểu rằng Fixture có thể trả về bất cứ kiểu dữ liệu nào (Số, Chuỗi, Object...), và chúng ta có thể dùng nhiều fixture cùng lúc.


Bước 1: Viết Sổ Tay Công Thức (Fixture Definition)

"Hãy nhìn vào file basic.fixture.ts dưới đây. Đây là lúc chúng ta nạp 'Menu' vào đầu Robot."

import { test as base } from '@playwright/test';

// 1. MENU (Định nghĩa kiểu dữ liệu)

// Robot cần biết nó sắp phục vụ những món gì

export const test = base.extend<{

  randomNumber: number;  // Món 1: Số

  greeting: string;      // Món 2: Chữ

  userInfo: { name: string; age: number; email: string }; // Món 3: Object (Dữ liệu phức tạp)

}>({


  // 2. CÔNG THỨC CHẾ BIẾN (Implementation)


  // --- Món 1: Số ngẫu nhiên ---

  randomNumber: async ({}, use) => {

    // Chế biến trong bếp (Logic ẩn)

    const number = Math.floor(Math.random() * 100) + 1;

    // Bưng ra bàn

    await use(number);

  },

  // --- Món 2: Lời chào ---

  greeting: async ({}, use) => {

    const message = 'Hello, World!';

    await use(message);

  },


  // --- Món 3: Thông tin User ---

  userInfo: async ({}, use) => {

    const user = {

      name: 'John Doe',

      age: 30,

      email: 'john@example.com',

    };

    await use(user);

  },

});

 

 

Phân tích: Sự kỳ diệu của "Merge" (Gộp Kỹ Năng)

"Các bạn có thắc mắc: Tại sao chúng ta lại có thể dùng chung randomNumber (mới) và page (cũ) trong cùng một bài test không?"

Đó là nhờ cơ chế Auto-Merge (Tự động gộp) của Playwright:

Mảnh ghép 1 (base): Chứa page, request, browser.

Mảnh ghép 2 (extend): Chứa randomNumber, greeting, userInfo.

Kết quả (test mới): Là một Siêu Robot biết làm TẤT CẢ các việc trên.


Bước 2: Khách gọi "Combo" (Sử dụng trong Test)

"Bây giờ, hãy xem cách Khách hàng (Hàm Test) gọi đồ ăn. Nó giống hệt việc đi ăn buffet: Thích món nào thì gắp món đó vào đĩa."

// Import con Robot 'test' đa năng chúng ta vừa tạo

import { test } from './fixtures/basic.fixture';

// KỊCH BẢN 1: Gọi hết các món mới

test('Test dùng toàn bộ Fixture mới', async ({ randomNumber, greeting, userInfo }) => {

  console.log(`Lời chào: ${greeting}`);         // Hello, World!

  console.log(`Số may mắn: ${randomNumber}`);   // Ví dụ: 42

  console.log(`User: ${userInfo.name}`);        // John Doe

});


// KỊCH BẢN 2: Gọi trộn lẫn (Món cũ + Món mới)

// Đây là điểm mạnh nhất! Ta dùng 'page' (của base) chung với 'userInfo' (của ta)

test('Test điền form User', async ({ page, userInfo }) => {
  // 1. Robot tự mở trình duyệt (page)

  await page.goto('https://example.com/register');

  // 2. Robot tự lấy data user (userInfo) để điền

  await page.fill('#name', userInfo.name);

  await page.fill('#email', userInfo.email);

  // Ta không cần phải tự khai báo biến const user = { ... } nữa!

});

 

Qua ví dụ này, các bạn cần nhớ 3 điều cốt lõi về Dependency Injection trong Playwright:

Đa dạng kiểu: Fixture không chỉ là page. Nó có thể là số, chuỗi, config, hoặc một cục data to đùng (userInfo).

Chọn lọc (Destructuring): Trong hàm test(..., async ({...}), bạn liệt kê cái gì thì Robot chỉ làm cái đó.

Nếu bạn chỉ viết ({ randomNumber }), Robot sẽ KHÔNG tốn công đi tạo userInfo. (Tiết kiệm tài nguyên).

Hòa nhập: Fixture của bạn và Fixture của Playwright sống chung hòa bình. Bạn không làm mất đi các tính năng cũ (page, request) khi tạo tính năng mới.

 

Teacher

Teacher

Nguyên Hoàng

Automation Engineer

With 7+ years of hands-on experience across multiple languages and frameworks. I'm here to share knowledge, helping you turn complex processes into simple and effective solutions.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học