Skip to content

fix(EdgedriverBinary): generate per-platform URLs, drop dead listing API#1026

Merged
elrrrrrrr merged 5 commits intomasterfrom
fix/edgedriver-listing-migration
Apr 8, 2026
Merged

fix(EdgedriverBinary): generate per-platform URLs, drop dead listing API#1026
elrrrrrrr merged 5 commits intomasterfrom
fix/edgedriver-listing-migration

Conversation

@elrrrrrrr
Copy link
Copy Markdown
Member

@elrrrrrrr elrrrrrrr commented Apr 8, 2026

Summary

EdgedriverBinary.fetch('/<version>/') has been broken since around 2026-04-07 — the legacy Azure Blob container listing API now returns HTTP 409 PublicAccessNotPermitted after Microsoft disabled public access on the storage account:

https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=<version>/&delimiter=/&comp=list
→ HTTP 409 PublicAccessNotPermitted

This made test/common/adapter/binary/EdgedriverBinary.test.ts > fetch() > should work fail on the items.length >= 3 assertion, blocking @eggjs/egg monorepo's E2E pipeline and any project depending on EdgedriverBinary.

Fix

Microsoft has migrated Edge WebDriver hosting to https://msedgedriver.microsoft.com/. Per-version files are still reachable at the same path pattern:

https://msedgedriver.microsoft.com/<version>/edgedriver_<platform>.zip

The new host also serves a listing.json dump (used by Microsoft's own catalog page at https://msedgedriver.microsoft.com/catalog/), but it has two drawbacks:

  1. It's a single static file containing every version since 112.0.1722.39 — ~9000 entries, ~1.2MB. I verified empirically that none of ?top / ?limit / ?max / ?pageSize / ?prefix / ?latest query parameters do anything — the response is byte-identical regardless of query string. There is no server-side filtering.
  2. Even with caching, that's 1.2MB extra per sync.

So instead this PR mirrors the approach already used by FirefoxBinary and ChromeForTestingBinary: don't call any listing API for sub-dirs at all. Instead, enumerate the per-version download URLs from a static list of known platform filenames:

const EDGEDRIVER_PLATFORM_FILES = [
  'edgedriver_arm64.zip',
  'edgedriver_linux64.zip',
  'edgedriver_mac64.zip',
  'edgedriver_mac64_m1.zip',
  'edgedriver_win32.zip',
  'edgedriver_win64.zip',
] as const;

Each emitted BinaryItem carries ignoreDownloadStatuses: [404], so any version that doesn't ship every platform (e.g. older builds without mac64_m1) is skipped cleanly by cnpmcore's sync pipeline rather than failing the task.

The root-dir listing path (#syncDirItemshttps://edgeupdates.microsoft.com/api/products) is completely unchanged — that JSON API still works.

Why not use listing.json at all?

Use listing.json Generate URLs (this PR)
Network per sub-dir fetch ~1.2MB JSON dump 0 bytes
Per-sync caching needed yes (~1.2MB cached + reset logic) no
Failure modes listing endpoint down → all sub-dir fetches fail per-platform 404 handled cleanly
Code surface listing parse + cache + error path one .map() over a const array
Singleton state safety needs careful reset in initFetch none, stateless
Platform discovery from server response static const (loses any net-new platform until updated)

The trade-off is that if Microsoft adds a new Edge WebDriver platform (e.g. linux_arm64), this adapter won't pick it up until the const list is updated. That's a maintenance cost but a small one — the platform list has been stable at six entries throughout the entire history of the new CDN.

What this PR does NOT change

  • Root-dir listing (fetch('/')): still uses https://edgeupdates.microsoft.com/api/products. Microsoft only exposes the very latest release of each channel via this API, so the result today is ~5 versions total (one per channel: Stable / Beta / Dev / Canary). Historical version coverage was — and still is — governed by what cnpmcore had previously synced, not by what edgeupdates returns now.
  • Version ordering in the root listing: items come out in the order Microsoft's API returns them, which is grouped by channel (Stable → Beta → Dev → Canary), newest within each channel. This is not a strict latest-first sort, but it matches the pre-PR behavior. If a strict ordering is desired, that's a separate change.

Impact on historical versions

Microsoft's per-version download URL works for versions from 112.0.1722.39 onwards. Versions older than 112 are no longer downloadable from any public Microsoft endpoint:

Version Old Blob URL New URL
80.0.361.111 409 PublicAccessNotPermitted 404
100.0.1185.27 409 PublicAccessNotPermitted 404
110.0.1587.69 409 PublicAccessNotPermitted 404
112.0.1722.39 409 PublicAccessNotPermitted 200 ✓
120.0.2210.133 409 PublicAccessNotPermitted 200 ✓
148.0.3966.0 409 PublicAccessNotPermitted 200 ✓

This is a Microsoft-side cutoff, not something cnpmcore can work around. Existing mirrored copies of pre-112 Edge WebDriver binaries in cnpmcore's own storage are unaffected — this PR only changes discovery of new files from upstream.

Side fix

Also adds await binary.initFetch() in the test's beforeEach. EdgedriverBinary is a @SingletonProto, so app.getEggObject(EdgedriverBinary) returns the same instance across tests, which means dirItems populated by one test would leak into the next. This was a pre-existing latent issue masked by the old test only having one it block, but it surfaces immediately when adding more cases.

Context / related issues

There is no formal Microsoft announcement for this specific storage lockdown, but the migration off azureedge.net and the gradual lockdown of the legacy XML listing have been documented across multiple community projects. Timeline:

Test plan

  • EdgedriverBinary.test.ts > should list recent stable versions from edgeupdates.microsoft.com — green
  • EdgedriverBinary.test.ts > should generate all known platform driver URLs for a version — green (asserts the exact 6 generated items with ignoreDownloadStatuses: [404] on each)
  • All 6 cnpmcore CI matrices (mysql node@22/24 × jsonBuilder true/false, postgresql node@22/24) green
  • test-deployment, typecheck (lint + fmtcheck + tsc), codecov/patch, codecov/project green
  • Manual smoke (suggested for reviewer): a fresh sync of a recent Edge WebDriver version (e.g. 148.0.3966.0) enumerates all 6 platform binaries and downloads all of them successfully, without 404s
  • Manual smoke (suggested for reviewer): a sync of an older version that doesn't ship every platform (e.g. 112.0.1722.39 has no mac64_m1) — the missing platform should be skipped via ignoreDownloadStatuses: [404] and the rest should complete

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of unavailable EdgeDriver versions for specific platforms—missing combinations are now skipped instead of causing failures.
  • Improvements

    • Enhanced EdgeDriver binary delivery infrastructure for greater reliability and consistency.

…isting.json

The old Azure Blob container listing API stopped working:

    https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=<version>/&delimiter=/&comp=list

started returning HTTP 409 `PublicAccessNotPermitted` around 2026-04-07,
after Microsoft disabled public access on the storage account. The old
`msedgedriver.azureedge.net` CDN had already stopped resolving in
July 2025 (see MicrosoftEdge/EdgeWebDriver#203,
MicrosoftEdge/EdgeWebDriver#201, seleniumbase/SeleniumBase#3888).
This broke `EdgedriverBinary.fetch()` for any version subpath and made
`test/common/adapter/binary/EdgedriverBinary.test.ts` fail on the
`items.length >= 3` assertion.

Microsoft now hosts Edge WebDriver downloads on
`msedgedriver.microsoft.com`, and the same host serves a single
JSON dump that replaces the Azure Blob XML listing:

    GET https://msedgedriver.microsoft.com/listing.json
    -> {
      items: [{ isDirectory, name, contentLength, lastModified }, ...],
      generatedAt: <iso8601>
    }

(This is the endpoint used by Microsoft's own catalog at
https://msedgedriver.microsoft.com/catalog/.) The adapter now fetches
this listing, filters by the requested version prefix, and exposes each
matching file as a `BinaryItem` pointing at
`https://msedgedriver.microsoft.com/<name>` for download.

Also mock the new endpoint in the EdgedriverBinary test with a fixture
(`test/fixtures/msedgedriver-listing.json`) so the test is no longer
dependent on live Microsoft endpoints.

Known coverage regression: the new `listing.json` only covers Edge
WebDriver versions from 112.0.1722.39 onwards (~2003 versions / 9000+
files as of 2026-04-08). Versions older than 112 are no longer
downloadable from any public Microsoft endpoint — they return HTTP
404 on `msedgedriver.microsoft.com/<old-version>/edgedriver_*.zip`
and HTTP 409 `PublicAccessNotPermitted` on the retired Azure Blob
container. This is a Microsoft-side change; cnpmcore users will have
to rely on previously mirrored copies for Edge WebDriver versions
< 112.

No formal Microsoft announcement for this specific storage lockdown,
but the migration has been discussed across multiple community
projects:
  - MicrosoftEdge/EdgeWebDriver#183 (azureedge.net CDN sunset,
    2025-01)
  - MicrosoftEdge/EdgeWebDriver#146 (blob listing stopped updating
    since 125.0, 2024-05)
  - MicrosoftEdge/EdgeWebDriver#201, #203 (azureedge.net
    unreachable, 2025-07/08)
  - seleniumbase/SeleniumBase#3888 (CDN moved to
    msedgedriver.microsoft.com, fixed in SeleniumBase 4.40.6)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

Replaces dynamic Azure-XML listing and parser in EdgeDriver binary retrieval with a deterministic generator of six platform ZIP entries per version; download URLs now use a fixed Microsoft CDN base, size/date set to '-' and missing artifacts are ignored via ignoreDownloadStatuses: [404].

Changes

Cohort / File(s) Summary
EdgeDriver Binary Implementation
app/common/adapter/binary/EdgedriverBinary.ts
Removed XML listing request and private XML parser; switched to deterministic generation of six platform ZIP items per version. Constructed URLs from https://msedgedriver.microsoft.com/<version>/<filename>. Set size and date to '-' and added ignoreDownloadStatuses: [404]. Removed node:path import.
EdgeDriver Binary Tests
test/common/adapter/binary/EdgedriverBinary.test.ts
Reset fetch cache with await binary.initFetch() before tests. Updated assertions to expect exact six-item list with fixed CDN URLs, size: '-', date: '-', and ignoreDownloadStatuses: [404] for each platform artifact.

Sequence Diagram(s)

(Skipped — changes are internal adapter logic and tests; do not introduce a multi-component runtime flow that benefits from a sequence diagram.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat: mirror edgewebdriver #676: Previously added the EdgedriverBinary implementation with XML listing parsing; this PR replaces that XML-based listing with deterministic per-platform entries.

Suggested reviewers

  • fengmk2

Poem

🐰 I hopped through code with ears so keen,
I swapped out XML for a tidy routine,
Six zips per version, URLs all neat,
Missing files politely skipped — no defeat,
A little rabbit clap for a simpler scene 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The PR title accurately describes the main change: switching EdgedriverBinary from fetching an Azure Blob XML listing API to generating per-platform URLs from a static list and removing the dead listing API code.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/edgedriver-listing-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@elrrrrrrr elrrrrrrr requested review from fengmk2 and killagu April 8, 2026 14:44
@elrrrrrrr elrrrrrrr added the enhancement New feature or request label Apr 8, 2026
@elrrrrrrr elrrrrrrr marked this pull request as ready for review April 8, 2026 14:45
Copilot AI review requested due to automatic review settings April 8, 2026 14:45
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.32%. Comparing base (407d659) to head (90ef54b).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1026      +/-   ##
==========================================
+ Coverage   94.12%   94.32%   +0.19%     
==========================================
  Files         211      211              
  Lines        8619     8664      +45     
  Branches     1672     1690      +18     
==========================================
+ Hits         8113     8172      +59     
+ Misses        506      492      -14     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the EdgedriverBinary adapter to use the new JSON-based listing API from Microsoft, replacing the deprecated Azure Blob XML API. The changes include new interfaces for the JSON structure and updated fetching logic. Feedback focuses on optimizing performance by caching the large listing file to avoid redundant network requests, improving error handling when the API fails, and leveraging base class helpers to reduce code duplication.

Comment on lines +193 to +195
if (!listing) {
return { items: [], nextParams: null };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Returning an empty items array when the listing fetch fails can be misleading, as it suggests the version exists but contains no files. It is safer to return undefined to signal a fetch failure. Additionally, verifying that listing.items exists before iterating prevents potential runtime errors if the API response structure is unexpected. Ensure that parsing of query parameters or API responses gracefully handles invalid inputs by providing sensible fallbacks or defaults.

Suggested change
if (!listing) {
return { items: [], nextParams: null };
}
if (!listing?.items) {
return;
}
References
  1. Ensure that parsing of query parameters gracefully handles invalid inputs (e.g., non-numeric values) by providing sensible fallbacks or defaults, rather than causing unexpected behavior.

@fengmk2
Copy link
Copy Markdown
Member

fengmk2 commented Apr 8, 2026

@codex review

The previous patch added a defensive branch in `#fetchListing()` that
logs a warning and returns `undefined` on non-200 responses, and a
matching `if (!listing)` branch in `fetch()` that returns an empty
`items` array. Neither was exercised by the existing happy-path test,
dropping patch coverage to ~82%.

Add a second test case that mocks `https://msedgedriver.microsoft.com/listing.json`
with `status: 500` and asserts that `fetch('/<version>/')` returns an
empty item list rather than throwing. This covers both uncovered
branches in a single case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes EdgedriverBinary.fetch('/<version>/') by switching Edge WebDriver subdirectory discovery from the now-blocked Azure Blob XML listing API to Microsoft’s new https://msedgedriver.microsoft.com/listing.json, and updates tests to avoid relying on live upstream endpoints.

Changes:

  • Update EdgedriverBinary to list per-version files by filtering msedgedriver.microsoft.com/listing.json and generating download URLs from msedgedriver.microsoft.com/<name>.
  • Update the EdgedriverBinary test to mock the new listing endpoint and adjust URL/date assertions.
  • Add a fixture JSON response for listing.json used by the test.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
app/common/adapter/binary/EdgedriverBinary.ts Switches version subdir listing to filter Microsoft’s listing.json and builds download URLs from the new host.
test/common/adapter/binary/EdgedriverBinary.test.ts Mocks listing.json and updates assertions for the new URL host and date format.
test/fixtures/msedgedriver-listing.json Adds a stable fixture response for listing.json used in tests.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34a47e14ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…reuse requestJSON, return undefined on failure

Apply three pieces of feedback from the PR review:

1. Cache the `listing.json` response per sync task.
   `listing.json` is a ~9000-entry dump (~500KB). A single sync task
   calls `fetch('/<version>/')` for many versions; previously each
   call would re-download the whole listing. Cache it in a
   promise-level field on the adapter and reset it in `initFetch` so
   each new sync task gets fresh data.

2. Return `undefined` on listing failure instead of `{ items: [] }`.
   The previous empty-items return value was ambiguous — callers
   couldn't tell "listing API is down" from "this version genuinely
   has no files". Returning `undefined` propagates the failure
   signal cleanly. Also guard against malformed responses by
   requiring `listing.items` to be an array before trusting it.

3. Reuse `AbstractBinary.requestJSON` instead of re-implementing
   `this.httpclient.request` with the same options. The helper
   already handles timeout / followRedirect / gzip / non-200
   warn logging.

Test updates:
- The existing non-200 case now asserts `result === undefined`.
- A new caching test mocks `listing.json` with a call counter and
  verifies two successive `fetch('/126.0.2578.0/')` calls produce
  identical items while only hitting the network once.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/common/adapter/binary/EdgedriverBinary.test.ts (2)

20-23: Add a regression test for a non-200 listing.json response.

These new lines only cover the success case. The outage path this PR is fixing can regress silently unless we also mock a 409/500 response and assert the empty result plus the warning log from #fetchListing().

As per coding guidelines "Use app.mockHttpclient('https://example.com/path', 'GET', { data, persist }) for HTTP mocking in tests" and "Assert logs using app.mockLog() followed by app.expectLog(/pattern/) for log assertions in tests."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/common/adapter/binary/EdgedriverBinary.test.ts` around lines 20 - 23,
Add a regression test in EdgedriverBinary.test that exercises the non-200 path
of `#fetchListing`(): use
app.mockHttpclient('https://msedgedriver.microsoft.com/listing.json', 'GET', {
data: <any>, persist: false }) to return a 409 (or 500) response and then call
the same code that invokes fetchListing(); assert it returns an empty result and
capture the warning by calling app.mockLog() before execution and
app.expectLog(/listing.*status|warning/i) (or similar pattern) afterwards;
ensure the test follows existing success-case structure and uses the same
helpers (app.mockHttpclient, app.mockLog, app.expectLog) to validate both the
empty return and the warning log from fetchListing().

64-76: Assert the exact child entries instead of broad regexes.

Lines 65-76 are loose enough that this still passes if a platform artifact disappears, if the URL loses the version prefix, or if the new direct-child filter starts leaking extra entries. Please lock this down with the fixture’s exact names/count/URLs, and add one nested or isDirectory: true fixture entry so the exclusion branch is exercised.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/common/adapter/binary/EdgedriverBinary.test.ts` around lines 64 - 76,
The test currently uses broad regex asserts on result.items which can hide
regressions; update the assertion in EdgedriverBinary.test.ts to assert the
exact expected child entries (names, urls, sizes, dates and isDir flags) and
exact length instead of regexes: build an expectedItems array and deep-compare
it to result.items (or compare lengths then iterate exact equality for each
field), and add one fixture entry with isDir: true (a nested directory) to
exercise the exclusion branch in the adapter logic; reference result.items and
the test assertions around item.name/item.url/item.isDir when making this
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/common/adapter/binary/EdgedriverBinary.ts`:
- Around line 192-195: The code repeatedly calls this.#fetchListing() (in
EdgedriverBinary) for every version-directory lookup, causing redundant network
requests; add an instance-level memoization field (e.g., a private property like
this.#listingCache) and have the method that currently calls
this.#fetchListing() first check and reuse the cached listing.json if present,
only invoking this.#fetchListing() to populate the cache on the first call;
ensure the cache is used across subdirectory/version fetches and
invalidated/refreshed only when needed.

---

Nitpick comments:
In `@test/common/adapter/binary/EdgedriverBinary.test.ts`:
- Around line 20-23: Add a regression test in EdgedriverBinary.test that
exercises the non-200 path of `#fetchListing`(): use
app.mockHttpclient('https://msedgedriver.microsoft.com/listing.json', 'GET', {
data: <any>, persist: false }) to return a 409 (or 500) response and then call
the same code that invokes fetchListing(); assert it returns an empty result and
capture the warning by calling app.mockLog() before execution and
app.expectLog(/listing.*status|warning/i) (or similar pattern) afterwards;
ensure the test follows existing success-case structure and uses the same
helpers (app.mockHttpclient, app.mockLog, app.expectLog) to validate both the
empty return and the warning log from fetchListing().
- Around line 64-76: The test currently uses broad regex asserts on result.items
which can hide regressions; update the assertion in EdgedriverBinary.test.ts to
assert the exact expected child entries (names, urls, sizes, dates and isDir
flags) and exact length instead of regexes: build an expectedItems array and
deep-compare it to result.items (or compare lengths then iterate exact equality
for each field), and add one fixture entry with isDir: true (a nested directory)
to exercise the exclusion branch in the adapter logic; reference result.items
and the test assertions around item.name/item.url/item.isDir when making this
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5983b8ef-f5b0-4b12-b0bd-9d7d0d6f4d67

📥 Commits

Reviewing files that changed from the base of the PR and between 6218873 and 13c0688.

📒 Files selected for processing (3)
  • app/common/adapter/binary/EdgedriverBinary.ts
  • test/common/adapter/binary/EdgedriverBinary.test.ts
  • test/fixtures/msedgedriver-listing.json

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/common/adapter/binary/EdgedriverBinary.ts (1)

15-24: Export the new listing contracts.

EdgedriverListingEntry and EdgedriverListing are file-local right now. Exporting them keeps the payload shape reusable for tests or sibling modules instead of forcing a second declaration later.

♻️ Proposed change
-interface EdgedriverListingEntry {
+export interface EdgedriverListingEntry {
   isDirectory: boolean;
   name: string;
   contentLength: number;
   lastModified: string;
 }
-interface EdgedriverListing {
+export interface EdgedriverListing {
   items: EdgedriverListingEntry[];
   generatedAt: string;
 }

As per coding guidelines, "Export types and interfaces for reusability across modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/common/adapter/binary/EdgedriverBinary.ts` around lines 15 - 24, The
interfaces EdgedriverListingEntry and EdgedriverListing are currently
file-local; export them so other modules/tests can import the payload shapes.
Update the declarations for EdgedriverListingEntry and EdgedriverListing to be
exported (e.g., export interface EdgedriverListingEntry { ... } and export
interface EdgedriverListing { ... }) and ensure any local usages still compile
after making them exported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/common/adapter/binary/EdgedriverBinary.ts`:
- Around line 223-228: The `#fetchListing` method caches the result in
`#listingPromise` but currently returns undefined for unusable/non-throwing
responses without clearing the cache, causing subsequent calls to reuse the bad
undefined; update `#fetchListing` (and related flow that returns undefined from
`#loadListing`) to clear/reset this.#listingPromise before returning undefined so
a future call will attempt a fresh fetch (mirror the existing cache-clearing
behavior used in the catch branch), referencing the `#fetchListing`,
`#listingPromise` and `#loadListing` symbols to locate and modify the logic.

---

Nitpick comments:
In `@app/common/adapter/binary/EdgedriverBinary.ts`:
- Around line 15-24: The interfaces EdgedriverListingEntry and EdgedriverListing
are currently file-local; export them so other modules/tests can import the
payload shapes. Update the declarations for EdgedriverListingEntry and
EdgedriverListing to be exported (e.g., export interface EdgedriverListingEntry
{ ... } and export interface EdgedriverListing { ... }) and ensure any local
usages still compile after making them exported.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2fd02f04-3f1d-4534-a889-8c6c656e46c8

📥 Commits

Reviewing files that changed from the base of the PR and between 13c0688 and 4bc09a9.

📒 Files selected for processing (2)
  • app/common/adapter/binary/EdgedriverBinary.ts
  • test/common/adapter/binary/EdgedriverBinary.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/common/adapter/binary/EdgedriverBinary.test.ts

… entirely

Previous attempt fetched `https://msedgedriver.microsoft.com/listing.json`
and filtered by version prefix. This had two problems:

1. `listing.json` is a ~1.2MB static dump containing every version since
   112.0.1722.39 (~9000 entries). It does not support any query/filter
   parameter — all of `?top`, `?limit`, `?max`, `?pageSize`, `?prefix`,
   etc. return the same 1212112-byte payload. A per-sync cache helps
   but still downloads 1.2MB per sync.
2. The promise-level cache in the previous commit leaked across tests
   because `EdgedriverBinary` is a `@SingletonProto` and the test
   suite shares one instance.

Instead, mirror the approach already used by `FirefoxBinary` and
`ChromeForTestingBinary`: don't call any listing API for sub-dirs at
all. Microsoft hosts per-version driver files at a stable URL pattern

    https://msedgedriver.microsoft.com/<version>/edgedriver_<platform>.zip

where `<platform>` is one of the six known values observed in the
current listing (arm64, linux64, mac64, mac64_m1, win32, win64). The
adapter now generates one `BinaryItem` per platform with
`ignoreDownloadStatuses: [404]`, so older versions that don't ship
every platform (or versions that are gated by Microsoft) are skipped
cleanly by cnpmcore's sync pipeline rather than failing the task.

Side benefits:
- 0 bytes downloaded per sub-dir fetch.
- No per-sync cache needed.
- No singleton state pollution between tests.
- Smaller surface area (removes ~60 lines of listing parse / fallback
  code, plus the XML `requestXml` dependency in this adapter).

Test changes:
- The happy path test now asserts the exact 6 generated items with
  `ignoreDownloadStatuses: [404]` on each.
- The "listing request fails" test is removed — there is no listing
  request anymore.
- The `beforeEach` calls `initFetch()` to reset `dirItems` between
  tests, so the singleton-shared state doesn't leak.
- The now-unused `test/fixtures/msedgedriver-listing.json` fixture is
  removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 15:12
@elrrrrrrr elrrrrrrr marked this pull request as draft April 8, 2026 15:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +6 to +20
// Microsoft moved Edge WebDriver binaries to https://msedgedriver.microsoft.com/
// in July 2025 after `msedgedriver.azureedge.net` was retired, and around
// 2026-04-07 also disabled public access on the legacy Azure Blob container
// that used to host the XML file listing. There is still no paginated/filtered
// listing API — the only "listing" endpoint on the new host is a ~1.2MB static
// JSON dump (`/listing.json`, ~9000 entries covering every version since
// 112.0.1722.39).
//
// To avoid hammering that 1.2MB dump for every version subdirectory during a
// sync, we mirror the approach used by `FirefoxBinary` / `ChromeForTestingBinary`
// and generate the per-version download URLs from a static list of known
// platform filenames. cnpmcore's sync pipeline honors the per-item
// `ignoreDownloadStatuses` field, so any version that doesn't ship a given
// platform (e.g. older builds without `edgedriver_mac64_m1.zip`) gets a clean
// 404 and is skipped rather than failing the sync.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR title/description indicate switching EdgeDriver subdir listing to https://msedgedriver.microsoft.com/listing.json, but the adapter now explicitly avoids any listing endpoint and generates a static set of platform URLs. Either update the PR metadata/rationale (and comments) to reflect the actual approach, or implement the listing.json-based discovery described in the PR to keep behavior and documentation aligned.

Copilot uses AI. Check for mistakes.
oxfmt prefers `(name) => ({...})` over `name => ({...})` for arrow
function arguments. Brought up by cnpmcore CI's `fmtcheck` step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@elrrrrrrr elrrrrrrr marked this pull request as ready for review April 8, 2026 15:32
Copilot AI review requested due to automatic review settings April 8, 2026 15:32
@elrrrrrrr elrrrrrrr changed the title fix(EdgedriverBinary): switch listing to msedgedriver.microsoft.com/listing.json fix(EdgedriverBinary): generate per-platform URLs, drop dead listing API Apr 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@elrrrrrrr elrrrrrrr merged commit b85f1cd into master Apr 8, 2026
21 checks passed
@elrrrrrrr elrrrrrrr deleted the fix/edgedriver-listing-migration branch April 8, 2026 15:37
fengmk2 pushed a commit that referenced this pull request Apr 8, 2026
[skip ci]

## <small>4.32.1 (2026-04-08)</small>

* fix(EdgedriverBinary): generate per-platform URLs, drop dead listing API (#1026) ([b85f1cd](b85f1cd)), closes [#1026](#1026)
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🎉 This PR is included in version 4.32.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

elrrrrrrr added a commit to eggjs/egg that referenced this pull request Apr 8, 2026
…ary fix

Picks up cnpm/cnpmcore#1026, which replaces the dead Azure Blob XML
listing API with directly-generated per-platform download URLs on
msedgedriver.microsoft.com. This unblocks the chore-ut-ci E2E run,
which has been failing on `EdgedriverBinary.test.ts > should work`
with `items.length >= 3` since 2026-04-08 04:19 UTC.

Old hash:  e82df3f4 (pre-fix)
New hash:  98463c33 (cnpmcore release v4.32.1, contains the merged PR)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
elrrrrrrr added a commit that referenced this pull request Apr 10, 2026
…API (#1026)

`EdgedriverBinary.fetch('/<version>/')` has been broken since around
**2026-04-07** — the legacy Azure Blob container listing API now returns
**HTTP 409 `PublicAccessNotPermitted`** after Microsoft disabled public
access on the storage account:

```
https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=<version>/&delimiter=/&comp=list
→ HTTP 409 PublicAccessNotPermitted
```

This made `test/common/adapter/binary/EdgedriverBinary.test.ts > fetch()
> should work` fail on the `items.length >= 3` assertion, blocking
`@eggjs/egg` monorepo's E2E pipeline and any project depending on
EdgedriverBinary.

Microsoft has migrated Edge WebDriver hosting to
`https://msedgedriver.microsoft.com/`. Per-version files are still
reachable at the same path pattern:

```
https://msedgedriver.microsoft.com/<version>/edgedriver_<platform>.zip
```

The new host **also** serves a `listing.json` dump (used by Microsoft's
own catalog page at https://msedgedriver.microsoft.com/catalog/), but it
has two drawbacks:

1. **It's a single static file containing every version since
`112.0.1722.39`** — ~9000 entries, ~1.2MB. I verified empirically that
**none of `?top` / `?limit` / `?max` / `?pageSize` / `?prefix` /
`?latest` query parameters do anything** — the response is
byte-identical regardless of query string. There is no server-side
filtering.
2. Even with caching, that's 1.2MB extra per sync.

So instead this PR mirrors the approach already used by
[`FirefoxBinary`](https://github.com/cnpm/cnpmcore/blob/master/app/common/adapter/binary/FirefoxBinary.ts)
and
[`ChromeForTestingBinary`](https://github.com/cnpm/cnpmcore/blob/master/app/common/adapter/binary/ChromeForTestingBinary.ts):
**don't call any listing API for sub-dirs at all.** Instead, enumerate
the per-version download URLs from a static list of known platform
filenames:

```ts
const EDGEDRIVER_PLATFORM_FILES = [
  'edgedriver_arm64.zip',
  'edgedriver_linux64.zip',
  'edgedriver_mac64.zip',
  'edgedriver_mac64_m1.zip',
  'edgedriver_win32.zip',
  'edgedriver_win64.zip',
] as const;
```

Each emitted `BinaryItem` carries `ignoreDownloadStatuses: [404]`, so
any version that doesn't ship every platform (e.g. older builds without
`mac64_m1`) is skipped cleanly by cnpmcore's sync pipeline rather than
failing the task.

The root-dir listing path (`#syncDirItems` →
`https://edgeupdates.microsoft.com/api/products`) is **completely
unchanged** — that JSON API still works.

| | Use `listing.json` | Generate URLs (this PR) |
|---|---|---|
| Network per sub-dir fetch | ~1.2MB JSON dump | **0 bytes** |
| Per-sync caching needed | yes (~1.2MB cached + reset logic) | no |
| Failure modes | listing endpoint down → all sub-dir fetches fail |
per-platform 404 handled cleanly |
| Code surface | listing parse + cache + error path | one `.map()` over
a const array |
| Singleton state safety | needs careful reset in `initFetch` | none,
stateless |
| Platform discovery | from server response | static const (loses any
net-new platform until updated) |

The trade-off is that if Microsoft adds a new Edge WebDriver platform
(e.g. `linux_arm64`), this adapter won't pick it up until the const list
is updated. That's a maintenance cost but a small one — the platform
list has been stable at six entries throughout the entire history of the
new CDN.

- **Root-dir listing (`fetch('/')`)**: still uses
`https://edgeupdates.microsoft.com/api/products`. Microsoft only exposes
the very latest release of each channel via this API, so the result
today is ~5 versions total (one per channel: Stable / Beta / Dev /
Canary). Historical version coverage was — and still is — governed by
what cnpmcore had previously synced, not by what edgeupdates returns
now.
- **Version ordering in the root listing**: items come out in the order
Microsoft's API returns them, which is grouped by channel (Stable → Beta
→ Dev → Canary), newest within each channel. This is *not* a strict
latest-first sort, but it matches the pre-PR behavior. If a strict
ordering is desired, that's a separate change.

Microsoft's per-version download URL works for **versions from
`112.0.1722.39` onwards**. Versions older than 112 are no longer
downloadable from any public Microsoft endpoint:

| Version | Old Blob URL | New URL |
|---|---|---|
| 80.0.361.111 | 409 PublicAccessNotPermitted | 404 |
| 100.0.1185.27 | 409 PublicAccessNotPermitted | 404 |
| 110.0.1587.69 | 409 PublicAccessNotPermitted | 404 |
| **112.0.1722.39** | 409 PublicAccessNotPermitted | **200 ✓** |
| 120.0.2210.133 | 409 PublicAccessNotPermitted | 200 ✓ |
| 148.0.3966.0 | 409 PublicAccessNotPermitted | 200 ✓ |

This is a Microsoft-side cutoff, not something cnpmcore can work around.
**Existing mirrored copies of pre-112 Edge WebDriver binaries in
cnpmcore's own storage are unaffected** — this PR only changes
*discovery* of new files from upstream.

Also adds `await binary.initFetch()` in the test's `beforeEach`.
`EdgedriverBinary` is a `@SingletonProto`, so
`app.getEggObject(EdgedriverBinary)` returns the same instance across
tests, which means `dirItems` populated by one test would leak into the
next. This was a pre-existing latent issue masked by the old test only
having one `it` block, but it surfaces immediately when adding more
cases.

There is no formal Microsoft announcement for this specific storage
lockdown, but the migration off `azureedge.net` and the gradual lockdown
of the legacy XML listing have been documented across multiple community
projects. Timeline:

- **2024-05** —
[MicrosoftEdge/EdgeWebDriver#146](MicrosoftEdge/EdgeWebDriver#146):
Azure Blob listing stopped updating for versions ≥ 125.0.
- **2025-01** —
[MicrosoftEdge/EdgeWebDriver#183](MicrosoftEdge/EdgeWebDriver#183):
`azureedge.net` CDN sunset (Edgio bankruptcy); Microsoft initially said
it would continue under a different CDN provider.
- **2025-07** —
[seleniumbase/SeleniumBase#3888](seleniumbase/SeleniumBase#3888),
[MicrosoftEdge/EdgeWebDriver#201](MicrosoftEdge/EdgeWebDriver#201),
[#203](MicrosoftEdge/EdgeWebDriver#203):
`msedgedriver.azureedge.net` DNS stopped resolving; new CDN is
`msedgedriver.microsoft.com`. SeleniumBase fixed this in 4.40.6.
- **2026-04-07** — (this PR): The Azure Blob container backing the old
XML listing also had public access disabled; only
`msedgedriver.microsoft.com` works now.

- [x] `EdgedriverBinary.test.ts > should list recent stable versions
from edgeupdates.microsoft.com` — green
- [x] `EdgedriverBinary.test.ts > should generate all known platform
driver URLs for a version` — green (asserts the exact 6 generated items
with `ignoreDownloadStatuses: [404]` on each)
- [x] **All 6 cnpmcore CI matrices** (`mysql node@22/24 × jsonBuilder
true/false`, `postgresql node@22/24`) green
- [x] `test-deployment`, `typecheck` (lint + fmtcheck + tsc),
`codecov/patch`, `codecov/project` green
- [ ] Manual smoke (suggested for reviewer): a fresh sync of a recent
Edge WebDriver version (e.g. 148.0.3966.0) enumerates all 6 platform
binaries and downloads all of them successfully, without 404s
- [ ] Manual smoke (suggested for reviewer): a sync of an older version
that doesn't ship every platform (e.g. 112.0.1722.39 has no `mac64_m1`)
— the missing platform should be skipped via `ignoreDownloadStatuses:
[404]` and the rest should complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Bug Fixes**
* Improved handling of unavailable EdgeDriver versions for specific
platforms—missing combinations are now skipped instead of causing
failures.

* **Improvements**
* Enhanced EdgeDriver binary delivery infrastructure for greater
reliability and consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants