chore: beautify install --list output (#36142)

This commit is contained in:
Yury Semikhatsky 2025-05-30 13:44:15 -07:00 committed by GitHub
parent 011050f51b
commit bc7dabece9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 87 additions and 55 deletions

View File

@ -34,7 +34,7 @@ import type { BrowserContext } from '../client/browserContext';
import type { BrowserType } from '../client/browserType';
import type { Page } from '../client/page';
import type { BrowserContextOptions, LaunchOptions } from '../client/types';
import type { Executable } from '../server';
import type { Executable, BrowserInfo } from '../server';
import type { TraceViewerServerOptions } from '../server/trace/viewer/traceViewer';
import type { Command } from '../utilsBundle';
@ -127,6 +127,62 @@ function checkBrowsersToInstall(args: string[], options: { noShell?: boolean, on
return executables;
}
function printInstalledBrowsers(browsers: BrowserInfo[]) {
const browserPaths = new Set<string>();
for (const browser of browsers)
browserPaths.add(browser.browserPath);
console.log(` Browsers:`);
for (const browserPath of [...browserPaths].sort())
console.log(` ${browserPath}`);
console.log(` References:`);
const references = new Set<string>();
for (const browser of browsers)
references.add(browser.referenceDir);
for (const reference of [...references].sort())
console.log(` ${reference}`);
}
function printGroupedByPlaywrightVersion(browsers: BrowserInfo[]) {
const dirToVersion = new Map<string, string>();
for (const browser of browsers) {
if (dirToVersion.has(browser.referenceDir))
continue;
const packageJSON = require(path.join(browser.referenceDir, 'package.json'));
const version = packageJSON.version;
dirToVersion.set(browser.referenceDir, version);
}
const groupedByPlaywrightMinorVersion = new Map<string, BrowserInfo[]>();
for (const browser of browsers) {
const version = dirToVersion.get(browser.referenceDir)!;
let entries = groupedByPlaywrightMinorVersion.get(version);
if (!entries) {
entries = [];
groupedByPlaywrightMinorVersion.set(version, entries);
}
entries.push(browser);
}
const sortedVersions = [...groupedByPlaywrightMinorVersion.keys()].sort((a, b) => {
const aComponents = a.split('.');
const bComponents = b.split('.');
const aMajor = parseInt(aComponents[0], 10);
const bMajor = parseInt(bComponents[0], 10);
if (aMajor !== bMajor)
return aMajor - bMajor;
const aMinor = parseInt(aComponents[1], 10);
const bMinor = parseInt(bComponents[1], 10);
if (aMinor !== bMinor)
return aMinor - bMinor;
return aComponents.slice(2).join('.').localeCompare(bComponents.slice(2).join('.'));
});
for (const version of sortedVersions) {
console.log(`\nPlaywright version: ${version}`);
printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version)!);
}
}
program
.command('install [browser...]')
@ -182,18 +238,8 @@ program
console.log(``);
}
} else if (options.list) {
const browsersMap = await registry.list();
for (const browserName in browsersMap) {
console.log(`Browser: ${browserName}`);
const browsers = browsersMap[browserName];
for (const browser of browsers) {
console.log(` Version: ${browser.browserVersion}`);
console.log(` Directory: ${browser.hostDir}`);
console.log(` Location: ${browser.browserPath}`);
console.log(``);
}
console.log(``);
}
const browsers = await registry.listInstalledBrowsers();
printGroupedByPlaywrightVersion(browsers);
} else {
const forceReinstall = hasNoArguments ? false : !!options.force;
await registry.install(executables, forceReinstall);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
export type { Executable } from './registry';
export type { BrowserInfo, Executable } from './registry';
export {
Registry,
installBrowsersForNpmInstall,

View File

@ -449,11 +449,11 @@ type BrowsersJSONDescriptor = {
dir: string,
};
type BrowsersInfo = {
export type BrowserInfo = {
browserName: string,
browserVersion: number,
hostDir: string,
browserPath: string
referenceDir: string,
};
function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] {
@ -1050,9 +1050,8 @@ export class Registry {
await fs.promises.writeFile(path.join(linksDir, calculateSha1(PACKAGE_PATH)), PACKAGE_PATH);
// Remove stale browsers.
const [browsers, brokenLinks] = await this._traverseBrowserInstallations(linksDir);
await this._deleteStaleBrowsers(browsers);
await this._deleteBrokenInstallations(brokenLinks);
if (!getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_GC'))
await this._validateInstallationCache(linksDir);
// Install browsers for this package.
for (const executable of executables) {
@ -1117,9 +1116,7 @@ export class Registry {
}
// Remove stale browsers.
const [browsers, brokenLinks] = await this._traverseBrowserInstallations(linksDir);
await this._deleteStaleBrowsers(browsers);
await this._deleteBrokenInstallations(brokenLinks);
await this._validateInstallationCache(linksDir);
return {
numberOfBrowsersLeft: (await fs.promises.readdir(registryDirectory).catch(() => [])).filter(browserDirectory => isBrowserDirectory(browserDirectory)).length
@ -1260,25 +1257,20 @@ export class Registry {
}
}
async list() {
async listInstalledBrowsers() {
const linksDir = path.join(registryDirectory, '.links');
const [browsers] = await this._traverseBrowserInstallations(linksDir);
// Group browsers by browserName
const groupedBrowsers: Record<string, BrowsersInfo[]> = {};
for (const browser of browsers) {
if (!groupedBrowsers[browser.browserName])
groupedBrowsers[browser.browserName] = [];
groupedBrowsers[browser.browserName].push(browser);
}
return groupedBrowsers;
const { browsers } = await this._traverseBrowserInstallations(linksDir);
return browsers.filter(browser => fs.existsSync(browser.browserPath));
}
private async _traverseBrowserInstallations(linksDir: string): Promise<[browsers: BrowsersInfo[], brokenLinks: string[]]> {
const browserList: BrowsersInfo[] = [];
private async _validateInstallationCache(linksDir: string) {
const { browsers, brokenLinks } = await this._traverseBrowserInstallations(linksDir);
await this._deleteStaleBrowsers(browsers);
await this._deleteBrokenInstallations(brokenLinks);
}
private async _traverseBrowserInstallations(linksDir: string): Promise<{ browsers: BrowserInfo[], brokenLinks: string[] }> {
const browserList: BrowserInfo[] = [];
const brokenLinks: string[] = [];
for (const fileName of await fs.promises.readdir(linksDir)) {
const linkPath = path.join(linksDir, fileName);
@ -1302,8 +1294,8 @@ export class Registry {
browserList.push({
browserName,
browserVersion,
hostDir: linkTarget,
browserPath
browserPath,
referenceDir: linkTarget,
});
}
} catch (e) {
@ -1311,13 +1303,10 @@ export class Registry {
}
}
return [browserList, brokenLinks];
return { browsers: browserList, brokenLinks };
}
private async _deleteStaleBrowsers(browserList: BrowsersInfo[]) {
if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_GC'))
return;
private async _deleteStaleBrowsers(browserList: BrowserInfo[]) {
const usedBrowserPaths: Set<string> = new Set();
for (const browser of browserList) {
const { browserName, browserVersion, browserPath } = browser;

View File

@ -38,16 +38,13 @@ test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk
await test.step('playwright install --list', async () => {
const result = await exec('npx playwright install --list');
const listed = new Set();
const regex = /Browser:\s+([\w-]+)/g;
let match;
while ((match = regex.exec(result)) !== null)
listed.add(match[1]);
const expected = ['android', 'chromium', 'ffmpeg', 'firefox', 'webkit'];
expected.forEach(browser => {
expect(listed.has(browser)).toBe(true);
});
console.log('result', result);
expect.soft(result).toMatch(/Playwright version: \d+\.\d+/);
expect.soft(result).toMatch(/chromium-\d+/);
expect.soft(result).toMatch(/chromium_headless_shell-\d+/);
expect.soft(result).toMatch(/ffmpeg-\d+/);
expect.soft(result).toMatch(/firefox-\d+/);
expect.soft(result).toMatch(/webkit-\d+/);
});
await exec('node sanity.js playwright', { env: { PLAYWRIGHT_BROWSERS_PATH: '0' } });