Skip to content

Commit 2d60fc8

Browse files
Merge remote-tracking branch 'origin/main' into fix/playwright-guard-api-responses
2 parents 086cc55 + e5f28d1 commit 2d60fc8

3 files changed

Lines changed: 153 additions & 45 deletions

File tree

.github/scripts/build_playwright_shards.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,29 @@
6060
"search-rbac": 1,
6161
}
6262
TARGET_MS = 20 * 60 * 1000
63-
# The chromium lane has outgrown a 19-minute shard: at the COMMON_MAX_SHARDS
64-
# ceiling the heaviest shard is predicted at 19.2m, so full-mode planning aborts
65-
# and every merge-queue run fails before a single test runs. The binding limit
66-
# is the 25m `timeout` wrapper around `npx playwright test`, against which 21m
67-
# leaves ~4m; the 35m playwright-ci job clock is looser still, since
68-
# it also has to cover ~5-8m of setup and teardown around that wrapper.
69-
COMMON_SHARD_BUDGET_MS = 21 * 60 * 1000
63+
# Chromium shard budget, derived from the predicted→actual execution tail
64+
# rather than from the average. Measured on merge_group run 32219260486
65+
# (23 chromium shards, fresh auto-refreshed baseline): actual/predicted
66+
# execution ratio has median 0.96 — predictions are well calibrated — but
67+
# the tail reaches 1.08 on a healthy run and 1.23 on a noisy one
68+
# (chromium-01 in run 32209040146 was SIGTERM'd by the 25-minute wrapper
69+
# with 162/164 tests already passed). The binding constraint is that
70+
# budget × worst-tail-ratio must stay under the 1500-second wrapper:
71+
# 21 min × 1.23 = 1550 s → dead shard, all tests green (the outage)
72+
# 19 min × 1.23 = 1402 s → 98 s of margin
73+
# 19 min × 1.32 = 1500 s → break-even; tolerated tail is 1.32
74+
# 21 min was a stop-gap (#30784) to fit the lane under the old 24-shard
75+
# ceiling; the ceiling is now 28 (below), which is what actually makes a
76+
# 19-minute budget feasible again.
77+
COMMON_SHARD_BUDGET_MS = 19 * 60 * 1000
7078
EFFICIENCY = 0.85
71-
COMMON_MAX_SHARDS = 24
79+
# Raised 24 → 28 together with the budget revert above. Current chromium
80+
# content (~71,700 predicted worker-seconds) needs 25 shards at a
81+
# 19-minute budget — over the old cap, which is exactly why #30784 had to
82+
# raise the budget instead. 28 leaves ~12% content-growth headroom before
83+
# planning aborts; if the lane grows past that, split heavy suites (see
84+
# AUDITED_PARALLEL_SUITES) before considering another cap raise.
85+
COMMON_MAX_SHARDS = 28
7286
# Weight assigned to a test that has no timing evidence in `timing-baseline.json`
7387
# (or in any additional history payloads). Bumped from 20 s → 30 s alongside the
7488
# all-zero-history fix in `load_history`: a suite re-enabled after being

.github/scripts/tests/test_playwright_ci_planning.py

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,34 +40,40 @@ def test_basic_and_chromium_share_the_bounded_common_lane():
4040
planner = load_script("build_playwright_shards")
4141

4242
assert planner.PROJECT_LANES["Basic"] == "chromium"
43-
assert planner.lane_bounds("chromium", "full") == (5, 24)
43+
assert planner.lane_bounds("chromium", "full") == (
44+
5,
45+
planner.COMMON_MAX_SHARDS,
46+
)
4447

4548

46-
def test_full_common_shard_count_is_capped_at_24():
49+
def test_full_common_shard_count_is_capped_at_the_common_max():
4750
planner = load_script("build_playwright_shards")
51+
cap = planner.COMMON_MAX_SHARDS
52+
# Enough content to push the calculated shard count comfortably above
53+
# the cap — the exact figure is unimportant; we just need
54+
# `calculated > cap` so the min(cap, …) clamp is what returns.
4855
units = [
4956
planner.Unit(
5057
"chromium",
5158
f"{index}.spec.ts",
5259
str(index),
5360
weight_ms=1_000_000,
5461
)
55-
for index in range(81)
62+
for index in range(cap * 4)
5663
]
57-
units.append(
58-
planner.Unit("chromium", "remainder.spec.ts", "remainder", weight_ms=363_055)
59-
)
6064

61-
assert planner.shard_count(units, "chromium", "full") == 24
65+
assert planner.shard_count(units, "chromium", "full") == cap
6266

6367

6468
def test_common_lane_carries_its_own_shard_budget():
65-
# The chromium lane no longer sits a minute under TARGET_MS: the suite grew
66-
# past what COMMON_MAX_SHARDS could hold at 19m, so it now runs a minute
67-
# above the other lanes. Both still fit the 25m playwright timeout wrapper.
69+
# Chromium's budget is a minute UNDER the other lanes' TARGET_MS, derived
70+
# from the predicted→actual execution tail: actuals run up to 1.23× the
71+
# prediction on noisy runs (run 32209040146), and 19 min × 1.32 is the
72+
# break-even against the 25-minute playwright wrapper. See the derivation
73+
# comment on COMMON_SHARD_BUDGET_MS.
6874
planner = load_script("build_playwright_shards")
6975

70-
assert planner.shard_budget_ms_for_lane("chromium") == 21 * 60 * 1000
76+
assert planner.shard_budget_ms_for_lane("chromium") == 19 * 60 * 1000
7177
assert planner.shard_budget_ms_for_lane("search") == 20 * 60 * 1000
7278

7379

@@ -98,18 +104,26 @@ def test_common_assignment_stays_within_the_execution_ceiling():
98104
)
99105

100106

101-
def test_full_mode_chromium_converges_at_the_shard_ceiling():
102-
# Regression guard for the merge-queue outage: a lane of this shape exhausted
103-
# COMMON_MAX_SHARDS under the old 19m budget and aborted planning outright.
104-
# The allocator must converge at or before the ceiling, and the resulting
105-
# plan genuinely needs the window above 19m -- so quietly reverting the
106-
# budget to 19m fails on the final assertion rather than only in CI.
107+
def test_full_mode_chromium_converges_above_the_old_24_shard_ceiling():
108+
# Regression guard for BOTH historical outages on this lane:
109+
# 1. Under budget=19m × cap=24, content past ~1163 worker-minutes
110+
# exhausted the cap and aborted planning outright (the #30784
111+
# outage — its stop-gap was raising the budget to 21m).
112+
# 2. Under budget=21m, packed shards ran into the 25-minute wrapper on
113+
# tail-ratio runs (run 32209040146 — SIGTERM with 162/164 passed).
114+
# The durable configuration is budget=19m × cap=28: the allocator must
115+
# converge within the ceiling AND genuinely need more than 24 shards —
116+
# so quietly reverting COMMON_MAX_SHARDS to 24 fails here rather than
117+
# only in the merge queue. The content shape mirrors reality post
118+
# audit-splitting: many fine-grained units (1300 × 1 min ≈ today's
119+
# ~1200 worker-minutes of chromium content), not a few near-atomic
120+
# blocks whose granularity would distort LPT balance.
107121
planner = load_script("build_playwright_shards")
108122
units = [
109123
planner.Unit(
110-
"chromium", f"heavy-{index}.spec.ts", str(index), weight_ms=13 * 60 * 1000
124+
"chromium", f"fine-{index}.spec.ts", str(index), weight_ms=60_000
111125
)
112-
for index in range(96)
126+
for index in range(1300)
113127
]
114128

115129
shards = planner.assign_lane_within_budget(units, "chromium", "full")
@@ -120,7 +134,7 @@ def test_full_mode_chromium_converges_at_the_shard_ceiling():
120134
)
121135
assert len(shards) <= planner.COMMON_MAX_SHARDS
122136
assert heaviest_ms <= planner.COMMON_SHARD_BUDGET_MS
123-
assert heaviest_ms > 19 * 60 * 1000
137+
assert len(shards) > 24
124138

125139

126140
def test_full_mode_chromium_reports_a_lane_the_ceiling_cannot_hold():
@@ -132,7 +146,7 @@ def test_full_mode_chromium_reports_a_lane_the_ceiling_cannot_hold():
132146
for index in range(120)
133147
]
134148

135-
with pytest.raises(SystemExit, match=r"needs more than 24 shards"):
149+
with pytest.raises(SystemExit, match=r"needs more than 28 shards"):
136150
planner.assign_lane_within_budget(units, "chromium", "full")
137151

138152

@@ -992,27 +1006,27 @@ def test_hook_heavy_subsuites_in_audited_suite_stay_atomic():
9921006
]
9931007

9941008

995-
def test_common_shards_enforce_the_twenty_one_minute_budget(tmp_path):
1009+
def test_common_shards_enforce_the_nineteen_minute_budget(tmp_path):
9961010
planner = load_script("build_playwright_shards")
9971011
within_budget = planner.Unit(
9981012
"chromium",
9991013
"within.spec.ts",
10001014
"within",
10011015
grep_titles={("chromium", "within.spec.ts", "within")},
10021016
test_ids={"within"},
1003-
weight_ms=21 * 60 * 1000,
1017+
weight_ms=19 * 60 * 1000,
10041018
)
10051019
above_budget = planner.Unit(
10061020
"chromium",
10071021
"above.spec.ts",
10081022
"above",
10091023
grep_titles={("chromium", "above.spec.ts", "above")},
10101024
test_ids={"above"},
1011-
weight_ms=21 * 60 * 1000 + 1,
1025+
weight_ms=19 * 60 * 1000 + 1,
10121026
)
10131027

10141028
planner.write_plan(tmp_path, "chromium", 0, [within_budget])
1015-
with pytest.raises(SystemExit, match="above the 21-minute plan budget"):
1029+
with pytest.raises(SystemExit, match="above the 19-minute plan budget"):
10161030
planner.write_plan(tmp_path, "chromium", 1, [above_budget])
10171031

10181032

.github/workflows/playwright-e2e-reusable.yml

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -592,22 +592,53 @@ jobs:
592592
uses: actions/setup-node@v5
593593
with:
594594
node-version-file: openmetadata-ui/src/main/resources/ui/.nvmrc
595-
cache: yarn
596-
cache-dependency-path: openmetadata-ui/src/main/resources/ui/yarn.lock
595+
596+
# setup-node's built-in `cache: yarn` is exact-key only — a yarn.lock
597+
# change leaves the cache completely cold and yarn re-fetches all
598+
# ~2000 tarballs from the registry, maximizing exposure to transient
599+
# registry 5xx (run 32222855561: a single 502 on one tarball killed
600+
# planning for the whole queue entry). An explicit cache with
601+
# restore-keys falls back to the most recent lockfile's cache, so a
602+
# lockfile change only fetches the delta.
603+
- name: Restore yarn package cache
604+
uses: actions/cache@v5
605+
with:
606+
path: ~/.cache/yarn
607+
key: yarn-pkg-cache-${{ runner.os }}-${{ hashFiles('openmetadata-ui/src/main/resources/ui/yarn.lock') }}
608+
restore-keys: |
609+
yarn-pkg-cache-${{ runner.os }}-
597610
598611
- name: Install dependencies
599612
working-directory: openmetadata-ui/src/main/resources/ui
600613
run: |
601614
corepack enable
602-
yarn --ignore-scripts --frozen-lockfile
615+
# Retry transient registry failures (5xx on tarball fetches) —
616+
# yarn v1 does not retry HTTP errors on its own.
617+
for attempt in 1 2 3; do
618+
yarn --ignore-scripts --frozen-lockfile --network-timeout 100000 && break
619+
if [[ "$attempt" -eq 3 ]]; then
620+
echo "yarn install failed after 3 attempts" >&2
621+
exit 1
622+
fi
623+
echo "::warning::yarn install attempt $attempt failed (registry blip?); retrying in $((attempt * 15))s"
624+
sleep $((attempt * 15))
625+
done
603626
627+
# These two steps SEED the run-scoped browser cache for the 30+ shard
628+
# jobs downstream: the plan job finishes before any shard starts, so
629+
# on a yarn.lock change the new browser-cache key is populated exactly
630+
# once here instead of every shard downloading ~250 MB of Chromium in
631+
# parallel. `--list` itself never launches a browser, so a seeding
632+
# failure must not fail planning — shards self-install as a fallback.
604633
- name: Seed Playwright browser cache
634+
continue-on-error: true
605635
uses: actions/cache@v5
606636
with:
607637
path: ~/.cache/ms-playwright
608638
key: ${{ runner.os }}-playwright-${{ hashFiles('openmetadata-ui/src/main/resources/ui/yarn.lock') }}
609639

610640
- name: Install cached Chromium
641+
continue-on-error: true
611642
working-directory: openmetadata-ui/src/main/resources/ui
612643
run: npx playwright install chromium
613644

@@ -865,15 +896,32 @@ jobs:
865896
uses: actions/setup-node@v5
866897
with:
867898
node-version-file: openmetadata-ui/src/main/resources/ui/.nvmrc
868-
cache: yarn
869-
cache-dependency-path: openmetadata-ui/src/main/resources/ui/yarn.lock
899+
900+
# restore-keys fallback + retry — see the rationale on the
901+
# plan-playwright job's yarn cache step.
902+
- name: Restore yarn package cache
903+
if: ${{ needs.restore-playwright-fixture.outputs.fixture_cache_hit != 'true' || (needs.plan-playwright.outputs.requires_airflow == 'true' && needs.restore-playwright-fixture.outputs.ingestion_cache_hit != 'true') }}
904+
uses: actions/cache@v5
905+
with:
906+
path: ~/.cache/yarn
907+
key: yarn-pkg-cache-${{ runner.os }}-${{ hashFiles('openmetadata-ui/src/main/resources/ui/yarn.lock') }}
908+
restore-keys: |
909+
yarn-pkg-cache-${{ runner.os }}-
870910
871911
- name: Install fixture-state dependencies
872912
if: ${{ needs.restore-playwright-fixture.outputs.fixture_cache_hit != 'true' || (needs.plan-playwright.outputs.requires_airflow == 'true' && needs.restore-playwright-fixture.outputs.ingestion_cache_hit != 'true') }}
873913
working-directory: openmetadata-ui/src/main/resources/ui
874914
run: |
875915
corepack enable
876-
yarn --ignore-scripts --frozen-lockfile
916+
for attempt in 1 2 3; do
917+
yarn --ignore-scripts --frozen-lockfile --network-timeout 100000 && break
918+
if [[ "$attempt" -eq 3 ]]; then
919+
echo "yarn install failed after 3 attempts" >&2
920+
exit 1
921+
fi
922+
echo "::warning::yarn install attempt $attempt failed (registry blip?); retrying in $((attempt * 15))s"
923+
sleep $((attempt * 15))
924+
done
877925
878926
- name: Restore Playwright browser cache
879927
if: ${{ needs.restore-playwright-fixture.outputs.fixture_cache_hit != 'true' || (needs.plan-playwright.outputs.requires_airflow == 'true' && needs.restore-playwright-fixture.outputs.ingestion_cache_hit != 'true') }}
@@ -1209,15 +1257,31 @@ jobs:
12091257
uses: actions/setup-node@v5
12101258
with:
12111259
node-version-file: "openmetadata-ui/src/main/resources/ui/.nvmrc"
1212-
cache: yarn
1213-
cache-dependency-path: openmetadata-ui/src/main/resources/ui/yarn.lock
1260+
1261+
# restore-keys fallback + retry — see the rationale on the
1262+
# plan-playwright job's yarn cache step.
1263+
- name: Restore yarn package cache
1264+
uses: actions/cache@v5
1265+
with:
1266+
path: ~/.cache/yarn
1267+
key: yarn-pkg-cache-${{ runner.os }}-${{ hashFiles('openmetadata-ui/src/main/resources/ui/yarn.lock') }}
1268+
restore-keys: |
1269+
yarn-pkg-cache-${{ runner.os }}-
12141270
12151271
- name: Install dependencies
12161272
id: install-dependencies
12171273
working-directory: openmetadata-ui/src/main/resources/ui/
12181274
run: |
12191275
corepack enable
1220-
yarn --ignore-scripts --frozen-lockfile
1276+
for attempt in 1 2 3; do
1277+
yarn --ignore-scripts --frozen-lockfile --network-timeout 100000 && break
1278+
if [[ "$attempt" -eq 3 ]]; then
1279+
echo "yarn install failed after 3 attempts" >&2
1280+
exit 1
1281+
fi
1282+
echo "::warning::yarn install attempt $attempt failed (registry blip?); retrying in $((attempt * 15))s"
1283+
sleep $((attempt * 15))
1284+
done
12211285
12221286
- name: Restore Playwright browser cache
12231287
id: restore-browser
@@ -1638,14 +1702,30 @@ jobs:
16381702
uses: actions/setup-node@v5
16391703
with:
16401704
node-version-file: openmetadata-ui/src/main/resources/ui/.nvmrc
1641-
cache: yarn
1642-
cache-dependency-path: openmetadata-ui/src/main/resources/ui/yarn.lock
1705+
1706+
# restore-keys fallback + retry — see the rationale on the
1707+
# plan-playwright job's yarn cache step.
1708+
- name: Restore yarn package cache
1709+
uses: actions/cache@v5
1710+
with:
1711+
path: ~/.cache/yarn
1712+
key: yarn-pkg-cache-${{ runner.os }}-${{ hashFiles('openmetadata-ui/src/main/resources/ui/yarn.lock') }}
1713+
restore-keys: |
1714+
yarn-pkg-cache-${{ runner.os }}-
16431715
16441716
- name: Install dependencies
16451717
working-directory: openmetadata-ui/src/main/resources/ui/
16461718
run: |
16471719
corepack enable
1648-
yarn --ignore-scripts --frozen-lockfile
1720+
for attempt in 1 2 3; do
1721+
yarn --ignore-scripts --frozen-lockfile --network-timeout 100000 && break
1722+
if [[ "$attempt" -eq 3 ]]; then
1723+
echo "yarn install failed after 3 attempts" >&2
1724+
exit 1
1725+
fi
1726+
echo "::warning::yarn install attempt $attempt failed (registry blip?); retrying in $((attempt * 15))s"
1727+
sleep $((attempt * 15))
1728+
done
16491729
16501730
- name: Download blob reports
16511731
uses: actions/download-artifact@v7

0 commit comments

Comments
 (0)