diff --git a/app/common/adapter/binary/EdgedriverBinary.ts b/app/common/adapter/binary/EdgedriverBinary.ts
index e0652677..d0bc928f 100644
--- a/app/common/adapter/binary/EdgedriverBinary.ts
+++ b/app/common/adapter/binary/EdgedriverBinary.ts
@@ -1,10 +1,35 @@
-import path from 'node:path';
-
import { SingletonProto } from 'egg';
import { BinaryType } from '../../enum/Binary.ts';
import { AbstractBinary, BinaryAdapter, type BinaryItem, type FetchResult } from './AbstractBinary.ts';
+// 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.
+const EDGEDRIVER_DOWNLOAD_BASE = 'https://msedgedriver.microsoft.com/';
+// Platform filenames observed in Microsoft's current `listing.json` dump.
+// Every version since 112.0.1722.39 ships some subset of these six files.
+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;
+
@SingletonProto()
@BinaryAdapter(BinaryType.Edgedriver)
export class EdgedriverBinary extends AbstractBinary {
@@ -168,52 +193,20 @@ export class EdgedriverBinary extends AbstractBinary {
return { items: this.dirItems?.[dir] ?? [], nextParams: null };
}
- // fetch sub dir
- // /foo/ => foo/
+ // fetch sub dir: generate the known platform filenames for this version.
+ // We intentionally don't call any listing API — see the file-level
+ // comment for the rationale. Any platform that doesn't exist for a
+ // specific version is skipped cleanly via `ignoreDownloadStatuses`.
+ // /126.0.2578.0/ => 126.0.2578.0/
const subDir = dir.slice(1);
- // https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=124.0.2478.97/&delimiter=/&maxresults=100&restype=container&comp=list
- const url = `https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=${encodeURIComponent(subDir)}&delimiter=/&maxresults=100&restype=container&comp=list`;
- const xml = await this.requestXml(url);
- return { items: this.#parseItems(xml), nextParams: null };
- }
-
- #parseItems(xml: string): BinaryItem[] {
- const items: BinaryItem[] = [];
- // 124.0.2478.97/edgedriver_arm64.ziphttps://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zipFri, 10 May 2024 18:35:44 GMT0x8DC712000713C139191362application/octet-stream1tjPTf5JU6KKB06Qf1JOGw==BlockBlobunlocked
- const fileRe =
- /([^<]+?)<\/Name>([^<]+?)<\/Url>([^<]+?)<\/Last-Modified>(?:[^<]+?)<\/Etag>(\d+)<\/Content-Length>/g;
- const matchItems = xml.matchAll(fileRe);
- for (const m of matchItems) {
- const fullname = m[1].trim();
- //
- // 124.0.2478.97/edgedriver_arm64.zip
- // https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zip
- //
- // Fri, 10 May 2024 18:35:44 GMT
- // 0x8DC712000713C13
- // 9191362
- // application/octet-stream
- //
- //
- // 1tjPTf5JU6KKB06Qf1JOGw==
- //
- // BlockBlob
- // unlocked
- //
- //
- // ignore size = 0 dir
- const name = path.basename(fullname);
- const url = m[2].trim();
- const date = m[3].trim();
- const size = Number.parseInt(m[4].trim());
- items.push({
- name,
- isDir: false,
- url,
- size,
- date,
- });
- }
- return items;
+ const items: BinaryItem[] = EDGEDRIVER_PLATFORM_FILES.map((name) => ({
+ name,
+ isDir: false,
+ url: `${EDGEDRIVER_DOWNLOAD_BASE}${subDir}${name}`,
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ }));
+ return { items, nextParams: null };
}
}
diff --git a/test/common/adapter/binary/EdgedriverBinary.test.ts b/test/common/adapter/binary/EdgedriverBinary.test.ts
index 6c32ced3..4d937118 100644
--- a/test/common/adapter/binary/EdgedriverBinary.test.ts
+++ b/test/common/adapter/binary/EdgedriverBinary.test.ts
@@ -9,15 +9,19 @@ describe('test/common/adapter/binary/EdgedriverBinary.test.ts', () => {
let binary: EdgedriverBinary;
beforeEach(async () => {
binary = await app.getEggObject(EdgedriverBinary);
+ // EdgedriverBinary is a @SingletonProto — reset its per-sync cache so
+ // each test sees a fresh state (the first `fetch('/')` call populates
+ // `dirItems` which would otherwise persist across tests).
+ await binary.initFetch();
});
describe('fetch()', () => {
- it('should work', async () => {
+ it('should list recent stable versions from edgeupdates.microsoft.com', async () => {
app.mockHttpclient('https://edgeupdates.microsoft.com/api/products', 'GET', {
data: await TestUtil.readFixturesFile('edgeupdates.json'),
persist: false,
});
- let result = await binary.fetch('/');
+ const result = await binary.fetch('/');
assert.deepEqual(result, {
items: [
{
@@ -51,29 +55,69 @@ describe('test/common/adapter/binary/EdgedriverBinary.test.ts', () => {
],
nextParams: null,
});
+ });
- const latestVersion = result.items[result.items.length - 1].name;
- assert.ok(latestVersion);
- assert.equal(latestVersion, '126.0.2578.0/');
- result = await binary.fetch(`/${latestVersion}`);
+ it('should generate all known platform driver URLs for a version', async () => {
+ app.mockHttpclient('https://edgeupdates.microsoft.com/api/products', 'GET', {
+ data: await TestUtil.readFixturesFile('edgeupdates.json'),
+ persist: false,
+ });
+ const result = await binary.fetch('/126.0.2578.0/');
assert.ok(result);
- const items = result.items;
- assert.ok(items.length >= 3);
- for (const item of items) {
- // {
- // name: 'edgedriver_win64.zip',
- // isDir: false,
- // url: 'https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/126.0.2578.0/edgedriver_win64.zip',
- // size: 9564395,
- // date: 'Fri, 10 May 2024 17:04:10 GMT'
- // }
- assert.equal(item.isDir, false);
- assert.match(item.name, /^edgedriver_\w+.zip$/);
- assert.match(item.url, /^https:\/\//);
- assert.ok(typeof item.size === 'number');
- assert.ok(item.size > 0);
- assert.ok(item.date);
- }
+ assert.equal(result.nextParams, null);
+ // Expect all six known platform filenames, pointing at the new CDN,
+ // with `ignoreDownloadStatuses: [404]` so older versions that don't
+ // ship every platform get skipped cleanly instead of failing the sync.
+ assert.deepEqual(result.items, [
+ {
+ name: 'edgedriver_arm64.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_arm64.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ {
+ name: 'edgedriver_linux64.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_linux64.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ {
+ name: 'edgedriver_mac64.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_mac64.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ {
+ name: 'edgedriver_mac64_m1.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_mac64_m1.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ {
+ name: 'edgedriver_win32.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_win32.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ {
+ name: 'edgedriver_win64.zip',
+ isDir: false,
+ url: 'https://msedgedriver.microsoft.com/126.0.2578.0/edgedriver_win64.zip',
+ size: '-',
+ date: '-',
+ ignoreDownloadStatuses: [404],
+ },
+ ]);
});
});
});