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], + }, + ]); }); }); });