Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 41 additions & 48 deletions app/common/adapter/binary/EdgedriverBinary.ts
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +6 to +20
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.
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 {
Expand Down Expand Up @@ -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[] = [];
// <Blob><Name>124.0.2478.97/edgedriver_arm64.zip</Name><Url>https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zip</Url><Properties><Last-Modified>Fri, 10 May 2024 18:35:44 GMT</Last-Modified><Etag>0x8DC712000713C13</Etag><Content-Length>9191362</Content-Length><Content-Type>application/octet-stream</Content-Type><Content-Encoding /><Content-Language /><Content-MD5>1tjPTf5JU6KKB06Qf1JOGw==</Content-MD5><Cache-Control /><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus></Properties></Blob>
const fileRe =
/<Blob><Name>([^<]+?)<\/Name><Url>([^<]+?)<\/Url><Properties><Last-Modified>([^<]+?)<\/Last-Modified><Etag>(?:[^<]+?)<\/Etag><Content-Length>(\d+)<\/Content-Length>/g;
const matchItems = xml.matchAll(fileRe);
for (const m of matchItems) {
const fullname = m[1].trim();
// <Blob>
// <Name>124.0.2478.97/edgedriver_arm64.zip</Name>
// <Url>https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zip</Url>
// <Properties>
// <Last-Modified>Fri, 10 May 2024 18:35:44 GMT</Last-Modified>
// <Etag>0x8DC712000713C13</Etag>
// <Content-Length>9191362</Content-Length>
// <Content-Type>application/octet-stream</Content-Type>
// <Content-Encoding/>
// <Content-Language/>
// <Content-MD5>1tjPTf5JU6KKB06Qf1JOGw==</Content-MD5>
// <Cache-Control/>
// <BlobType>BlockBlob</BlobType>
// <LeaseStatus>unlocked</LeaseStatus>
// </Properties>
// </Blob>
// 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 };
}
}
90 changes: 67 additions & 23 deletions test/common/adapter/binary/EdgedriverBinary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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],
},
]);
});
});
});
Loading