test.fail() in after hooks relabels an already-executed test and can make a failing run exit 0
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 72/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- playwright, typescript
- Lĩnh vực
- testing
Hướng nghiên cứu
Start by reading _modifier and the existing skip handling changed in #43713, then trace how expectedStatus affects test results after after hooks and fixture teardown. Reproduce the issue with the provided test.afterEach() example and a failing assertion. Done means a late test.fail() is reported as a failure without changing the completed test’s expected status, while a declared test.fail('title', fn) still behaves as expected.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
test.fail() in after hooks relabels an already-executed test and can make a failing run exit 0
Found while qualifying Playwright's modifier handling for 9l (9lives), as a sibling of #43676 (fixed by #43713). 9l assesses Playwright suites and consumes their execution results, so a modifier that changes a completed test's expectedStatus after execution directly corrupts execution evidence.
Reproduction (verified on 1.61.1, 1.64.1 and 1.65.0-alpha-2026-10-28)
import {test, expect} from '@playwright/test';
test.afterEach(() => {
test.fail();
});
test('genuinely failing', () => {
expect(1).toBe(2);
});
$ npx playwright test a.spec.ts
✘ 1 a.spec.ts:7:5 › genuinely failing (3ms)
1 passed (0.3s)
$ echo $?
0
The test visibly fails, yet the summary says 1 passed and the process exit code is 0.
What happens
test.fail()called in anafterEach(alsoafterAll, also an auto-fixture teardown) runs after the test body finished._modifiersetstestInfo.expectedStatus = 'failed'directly — no after-hook guard, and unlike skip/fixme it does not throw.- A failing test:
status = failednow equalsexpectedStatus = failed→ outcomeexpected→ green exit code. A real failure is indistinguishable from a declared expected failure. - A passing test: relabeled
unexpected→ the run fails retroactively.
| Scenario | Result |
|---|---|
failing body + test.fail() in afterEach |
expected, exit 0 |
failing body + test.fail() in afterAll |
expected, exit 0 |
failing body + test.fail() in fixture teardown |
expected, exit 0 |
same with --retries=1 |
exit 0 |
passing body + test.fail() in afterEach |
unexpected, exit 1 |
control: declared test.fail('title', fn) that fails |
expected, exit 0 (correct) |
Why #43713 does not cover it
#43713 sets testInfo._allowSkips = false before after hooks, which only affects the TestSkipError path (skip/fixme). I verified this by applying the same one-line patch locally: test.skip(true, ...) in afterEach is then correctly rejected (exit 1), while test.fail() in afterEach still relabels and exits 0.
The fail branch of _modifier needs the same treatment: after the test has finished executing, it must not mutate expectedStatus — it should be rejected (reported as a failure), as #43713 does for skips.
Impact for downstream tooling
Any consumer of Playwright results (reporters, CI gates, evidence tools) reads expected as "intentional failure". With one stray test.fail() in a shared after hook — or any code reaching it through a fixture teardown — genuine failures pass CI silently. The relabeled annotation is also attached to the finished test, leaving no trace that the expectation was set after execution.
- Ngôn ngữ chính
- TypeScript
- Star
- 98.5k
- Fork
- 6.7k
- Merge trung bình
- 15 giờ 42 phút
- Pull request đã merge (30 ngày)
- 313
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của microsoft/playwright
-
needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
microsoft/playwright#43762 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Feature]: Node 26 Docker ImagesĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
microsoft/playwright#43759 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
microsoft/playwright#43741 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: Uncaught TypeError in captureSecurityDetails when APIRequestContext reuses a destroyed keep-alive socketCó thể đã có người làm @microsoft-playwright-automation đã nhận 2 ngày trước. Đang mởneeds-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
microsoft/playwright#43728 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Feature]: Set `this.name` in `TargetClosedError` constructorCó thể làm lại được @LeonxLJX đã nhận 56 ngày trước và không có pull request nào đang mở. Đang mởP3-collecting-feedback
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 86/100
microsoft/playwright#42522 · 7 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của microsoft/playwright
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
components-web-app/docs#307 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
localstack/localstack-mcp-server#112 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area:frontend kind:bug priority:p1 ready
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug ready-for-agent
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 73/100
PhyberApex/kuroshiro#1687 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Performance (Web Vitals) collector: LCP/FCP from background tabs are not discarded, inflating p75Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
umami-software/umami#4720 ·
Maintainer thường phản hồi trong vòng 3 ngày