Hỏi đáp - case study thực tế

[Thảo luận]: Tests fail khi chạy cả suite nhưng pass khi chạy riêng lẻ 

Phong Do Phong Do · 24/02/2025 · 13 phút đọc

Một cái discussion khá hay trên discord community. Có anh em nào cũng gặp tình trạng tương tự rồi chưa?

lrz_qa: Xin chào mọi người 👋, tôi là newbie Playwright và đã tạo một số UI tests. Các tests này pass khi tôi chạy từng cái một ✅, nhưng khi chạy chúng như một suite thì một số tests fail vì không tìm thấy locators 😓. Các tests này độc lập với nhau. Có ai từng gặp vấn đề này chưa? 🤔 Có tài liệu nào mà ai đó có thể chỉ cho tôi để tôi hiểu rõ chuyện gì đang xảy ra và sửa lỗi này không? 📚 Mục tiêu của tôi là chạy tất cả tests này như một regression test suite 🚀.

SinFluxx: Bạn có thể chia sẻ code của mình được không? 👀 Bạn đã thử chạy tests ở UI mode hay ghi lại traces để xem chuyện gì đang xảy ra chưa? 🎥

jrmouta: Có phải vấn đề concurrency không nhỉ? ⚙️

PramodYadav: Vậy chúng fail ở môi trường local hay trên CI? 🌍 Hay cả hai? 🤷‍♂️

devik: Có thể là vấn đề performance không? ⏱️


refactoreric — 1/2/25, 03:18
Như @SinFluxx cũng gợi ý, việc ghi lại trace và phân tích chuyện gì đang xảy ra bằng trace viewer là cách tốt nhất 👍.
📎 https://playwright.dev/docs/trace-viewer-intro

lrz_qa (OP) — 1/2/25, 06:21
Cảm ơn mọi người nhé! 🙏 Tests của tôi fail ở local và tôi đã thử chạy tests ở headed mode, nhưng nó chạy nhanh quá nên khó mà thấy được lỗi gì đang xảy ra 😅. Tôi sẽ thử trace-viewer xem có tìm ra kết quả gì không.

qm84 — 3/2/25, 01:29
Bạn có ví dụ nào về test của mình để chia sẻ không? 🤔 Khi mới dùng Playwright, tôi từng gặp vấn đề vì dùng await page.locator("#myId").isVisible() thay vì await expect(page.locator("#myId")).toBeVisible().

lrz_qa (OP) — 3/2/25, 08:02
Error: Timed out 5000ms chờ expect(locator).toHaveText(expected) ⏰
Locator: locator('.referencesheader')
Chuỗi mong đợi: “REFERENCES”
Nhận được:
Call log:

  • expect.toHaveText với timeout 5000ms
  • Đang chờ locator('.referencesheader')

Tôi thấy mình gặp các lỗi khác nhau, gần đây nhất là do timeout. Khi chạy test này单独 thì pass ✅, nhưng khi chạy cùng một loạt tests thì fail ❌. Tôi nên làm gì để tránh lỗi này xảy ra vậy?

refactoreric — 3/2/25, 12:45
Chào bạn, timeout không phải nguyên nhân, nó chỉ là triệu chứng thôi ⛔. Bạn có thể dùng trace viewer để phân tích chuyện gì đang xảy ra, nếu bạn ghi lại trace files.
📎 https://playwright.dev/docs/trace-viewer-intro
Trace viewer | Playwright
Giới thiệu
Trace viewer | Playwright

lrz_qa (OP) — 3/2/25, 15:00
Cảm ơn mọi người! 🙌 Có vẻ như một webpage mới đang được mở trong lúc chạy test, và vì lý do nào đó, việc tìm locator lại diễn ra trên trang phụ này.
📷 [Image]
Khoảng 4.3 giây, tôi thấy một webpage mới được mở, làm sao để biết cú gọi nào gây ra chuyện này vậy? 🤔

SinFluxx — 3/2/25, 15:08
Nếu bạn bấm vào snapshot đó, ở dưới cửa sổ có các tab như Source/Call/Log, có thể chỉ ra cái gì đang gây ra đấy 👀.

SinFluxx — 3/2/25, 15:15
Tuy nhiên, trừ khi bạn tự tạo contexts/pages riêng thay vì dùng fixtures có sẵn, thì có lẽ là do website tự làm điều đó.

lrz_qa (OP) — 4/2/25, 17:08
Cảm ơn @SinFluxx, phần Source/Call không lộ ra gì rõ ràng cả 😕. Tôi sẽ đào sâu thêm chút nữa.

wafflesvsfrankie — 6/2/25, 06:57
Tôi cũng gặp vấn đề này, UI fail ngẫu nhiên. Rất muốn biết bạn tìm ra gì! 👀

lrz_qa (OP) — 7/2/25, 16:31
@wafflesvsfrankie, của bạn fail chỉ khi chạy tests liên tiếp hay là fail ngẫu nhiên ngay cả khi chạy riêng lẻ vậy? 🤔

NZCarnage — 18/2/25, 10:39
Khó chẩn đoán lắm nếu không có code test. Tôi đoán trace của bạn là khi chạy theo thứ tự so với chạy riêng lẻ. Tôi chỉ đoán bừa thôi, nhưng có thể tests không thực sự chạy tuần tự hoàn toàn. Tôi nghĩ page context của bạn đang giao tiếp với instance khác, nếu nhìn trace thì có vẻ ta thấy 2 tabs/sessions chạy cùng lúc ⚙️. Hãy xem lại code xem có gì khởi tạo page mới không và console.log khi điều này xảy ra. Nếu tests không chạy hoàn toàn tuần tự, tôi đoán nếu global memory bị chia sẻ, bạn không thực sự cách ly, nên tests ghi đè session. Tôi tò mò cách bạn chỉ định thứ tự chạy và code truyền page context thế nào, có thể vấn đề nằm ở đó.

Jon — 19/2/25, 20:09
Chào mọi người, tôi cũng gặp vấn đề tương tự. Tests chạy ngon khi chạy riêng lẻ nhưng fail khi chạy thành bộ 😓. Tôi đang thử debug và nhận ra một điều: tests của tôi fail sau khi Playwright chạy quá 30 giây. Đây là một test ví dụ mà tôi nhân bản 7-8 lần trong cùng bộ test, chỉ 5 cái pass, đến cái thứ 6 khi Playwright chạm mốc 30 giây thì fail.

test('Save & Publish Version', async ({ page }) => {
  await page.getByRole('link', { name: 'Pages' }).click()
  await page.getByRole('cell', { name: 'Homepage', exact: true }).click()
  await page.locator('#headlessui-popover-button-v-0-0-0').click()
  await page.getByRole('button', { name: 'Save & Publish Version' }).click()
  await page.getByRole('textbox', { name: 'Notes on changes for this' }).click()
  await page.getByRole('textbox', { name: 'Notes on changes for this' }).fill('Update Link Field')
  await page.getByRole('button', { name: 'Save', exact: true }).click();
  await page.getByText('Page Updated').waitFor({ state: 'visible' })
  await expect(page.getByText('Page Updated')).toBeVisible();
})

Có ai biết chuyện gì đang xảy ra không? @lrz_qa, vấn đề của bạn có giống tôi mô tả không? Có fail sau 30 giây chạy Playwright không? ⏰

SinFluxx — 19/2/25, 20:19
Bạn có chạy chúng song song không? Đã ghi trace cho tests chưa? 🎥

Jon — 19/2/25, 20:23
Không chạy song song, chúng tôi cần tests chạy serial vì sẽ gọi API để làm mới trạng thái database. Nếu chạy song song, tests chắc chắn fail do xung đột trạng thái database từ test trước 😅. Vậy nên phải chạy serial. Có phải vấn đề đã biết là Playwright fail khi chạy serial quá 30 giây không?

SinFluxx — 19/2/25, 20:24
Không đâu. 30 giây không phải timeout của test và một test của bạn đang tốn hơn 30 giây à? 🤔

Jon — 19/2/25, 20:25
Tôi chưa ghi trace, để tôi thử ngay bây giờ…

Jon — 19/2/25, 20:27
Như đã nói, để debug, tôi copy-paste cùng một test 7 lần vào một bộ test. 5 tests giống hệt nhau pass, mỗi cái mất 5-6 giây. Nhưng đến test thứ 6 (cũng y chang), nó fail, có vẻ vì Playwright chạy quá 30 giây và bị kẹt lại 😵. Tôi không cấu hình gì trong playwright.config để ảnh hưởng Serial Mode hay đặt timeout sau 30 giây cả. Kỳ lạ thật!

SinFluxx — 19/2/25, 20:28
Mặc định timeout cho một test là 30 giây, nếu bạn chưa đổi thì nó vẫn vậy đấy ⏳.

Jon — 19/2/25, 20:29
Sorry, tôi mới toanh với Playwright!! 😂 Nếu vậy thì có thể giải quyết được rồi!! Có tài liệu nào nói về cái này không, bạn chỉ tôi được không?

Jon — 19/2/25, 20:30
Khoan đã, bạn nói timeout 30 giây cho một test… Nhưng như tôi nói, tests của tôi pass trong 5 giây mà. Trong lúc debug, test thứ 6 trong bộ cũng đáng lẽ phải pass chứ.
📷 [Image]
Ý tôi là thế này này!

SinFluxx — 19/2/25, 20:31
Ừ, vì nó đã tốn hơn 30 giây rồi. Vấn đề là tại sao lại thế 🤔.

Jon — 19/2/25, 20:32
Đúng rồi, hiểu! Nhưng timeout 30 giây không phải vấn đề chính…

SinFluxx — 19/2/25, 20:32
Có chứ, ít nhất đó là thứ khiến nó fail đó.

Jon — 19/2/25, 20:32
Hiểu rồi, nhưng test本身 không có gì sai cả.

SinFluxx — 19/2/25, 20:32
Output của test chắc có thêm thông tin về chỗ nó bị kẹt, nếu bình thường chỉ tốn 5-6 giây thì sao lại vậy.

Jon — 19/2/25, 20:33
5 test trước pass đều là bản sao của test thứ 6, và nó luôn chết ở 30 giây 😵.

SinFluxx — 19/2/25, 20:33
Ừ vì 30 giây là timeout mặc định cho mỗi test mà ⏰.

Jon — 19/2/25, 20:34
Haha 😂. OK, vậy khi tôi chạy 5 tests = 30 giây runtime của Playwright đúng không?

SinFluxx — 19/2/25, 20:34
Không, 30 giây là cho mỗi test riêng lẻ.

Jon — 19/2/25, 20:35
Trời ơi, nhưng mỗi test không tốn 30 giây để chạy đâu mà!

SinFluxx — 19/2/25, 20:35
Tôi hiểu là các test pass của bạn chạy nhanh, nhưng vì lý do nào đó nó bị kẹt, nên cần xem output hoặc tốt hơn là ghi trace để biết thêm chi tiết 📈. Có lẽ không phải cả test chạy chậm hơn 30 giây, mà cái gì đó bất ngờ xảy ra khiến nó không làm được hành động nào đó và treo在那儿 tới khi hết 25 giây còn lại rồi dính timeout.

Jon — 19/2/25, 20:38
Để tôi thử lấy trace xem sao 🎥.

SinFluxx — 19/2/25, 20:39
Ví dụ: các hành động như fill() và click() mặc định không có timeout đâu nhé ⏳.

NZCarnage — 20/2/25, 01:47
Tôi nghĩ mọi người đã hiểu vấn đề 30 giây rồi 🫠. Bạn có thể tái hiện vấn đề bằng cách chạy một test với --repeat-each 10 từ command line. Thêm trace vào, bạn sẽ tìm được dòng code đang chờ cái gì đó. Thêm screenshot khi lỗi có thể làm mọi thứ rõ ràng hơn. Một điều cần biết: timeout của hành động là vô hạn nếu không chỉ định. Tôi từng viết tests tệ, cứ tưởng mọi thứ ổn nên ngồi chờ mãi. Rất flaky! Tôi khuyên: xem trace, tìm dòng chờ vô hạn, thêm timeout 5 giây. Test sẽ fail ở nguyên nhân gốc. Dùng repeat-each để xem có tái hiện thường xuyên không. Thêm code để deterministic hơn. Xem có gây timeout sau khi sửa không. Có thể có nhiều cách sửa, nhưng hãy cô lập nguyên nhân gốc trước rồi sửa. Sau khi sửa, có thể bỏ timeout ban đầu, nhưng để lại làm biện pháp an toàn cũng đáng.

NZCarnage — 20/2/25, 03:14
Cái này vừa mới xuất hiện: ⁠How to trigger flaky Playwright…

Jon — 21/2/25, 22:42
@SinFluxx @SinFluxx cảm ơn vì đã dành thời gian! 🙏 Chúng tôi phát hiện vấn đề (trong trường hợp của tôi) liên quan đến API request rate limiter là nguồn gốc vấn đề. Tôi có thể tìm ra nhanh hơn nếu bật Browser khi chạy tests. Tôi toàn chạy headless mode thôi 😅.

SinFluxx — 21/2/25, 23:00
Tuyệt, dù chạy headless thì cũng nên bật trace/video/screenshot khi fail để kiểm tra trực quan chuyện gì đang xảy ra nhé 👀.

refactoreric — 21/2/25, 23:45
Trace viewer thực sự là tính năng cứu tinh, làm Playwright nổi bật hơn các công cụ khác, nhưng vẫn thường bị bỏ qua. Một pipeline Playwright chưa hoàn thiện nếu không publish trace của tests fail 📈.
📎 https://playwright.dev/docs/trace-viewer-intro

NZCarnage — 22/2/25, 02:06
Tôi nghĩ template mặc định của Playwright nên đổi thành trace: 'retain-on-first-failure' làm mặc định. Vấn đề là hiện tại mặc định là on-first-retry, không phải vấn đề lớn, nhưng khi chạy local thì retry mặc định là 0. Trừ khi người dùng mới đổi cài đặt, họ sẽ không trải nghiệm hết. Có lẽ nên cập nhật best practices configuration để điều chỉnh template này.


Phong Do
Tác giả
Phong Do