/**
 * Copyright 2017 Google Inc. All rights reserved.
 * Modifications copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import fs from 'fs';
import os from 'os';
import path from 'path';
import * as util from 'util';
import { wrapInASCIIBox } from '@utils/ascii';
import { debugLogger } from '@utils/debugLogger';
import { shortPlatform, hostPlatform, isOfficiallySupportedPlatform } from '@utils/hostPlatform';
import { NET_DEFAULT_TIMEOUT } from '@utils/network';
import { spawnAsync } from '@utils/spawnAsync';
import { canAccessFile, existsAsync, removeFolders } from '@utils/fileUtils';
import { calculateSha1 } from '@utils/crypto';
import { getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand } from '@utils/env';
import { lock } from '@utils/third_party/lockfile';
import { fetchData } from '../utils';
import { getEmbedderName } from '../userAgent';
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from './dependencies';
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
import { packageRoot, binPath } from '../../package';
export { writeDockerVersion } from './dependencies';
const PACKAGE_PATH = packageRoot;
const BIN_PATH = binPath;
const PLAYWRIGHT_CDN_MIRRORS = [
    'https://cdn.playwright.dev/dbazure/download/playwright', // ESRP CDN
    'https://playwright.download.prss.microsoft.com/dbazure/download/playwright', // Directly hit ESRP CDN
    'https://cdn.playwright.dev', // Hit the Storage Bucket directly
];
if (process.env.PW_TEST_CDN_THAT_SHOULD_WORK) {
    for (let i = 0; i < PLAYWRIGHT_CDN_MIRRORS.length; i++) {
        const cdn = PLAYWRIGHT_CDN_MIRRORS[i];
        if (cdn !== process.env.PW_TEST_CDN_THAT_SHOULD_WORK) {
            const parsedCDN = new URL(cdn);
            parsedCDN.hostname = parsedCDN.hostname + '.does-not-resolve.playwright.dev';
            PLAYWRIGHT_CDN_MIRRORS[i] = parsedCDN.toString();
        }
    }
}
const EXECUTABLE_PATHS = {
    'chromium': {
        '<unknown>': undefined,
        'linux-x64': ['chrome-linux64', 'chrome'],
        'linux-arm64': ['chrome-linux', 'chrome'], // non-cft build
        'mac-x64': ['chrome-mac-x64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'],
        'mac-arm64': ['chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'],
        'win-x64': ['chrome-win64', 'chrome.exe'],
    },
    'chromium-headless-shell': {
        '<unknown>': undefined,
        'linux-x64': ['chrome-headless-shell-linux64', 'chrome-headless-shell'],
        'linux-arm64': ['chrome-linux', 'headless_shell'], // non-cft build
        'mac-x64': ['chrome-headless-shell-mac-x64', 'chrome-headless-shell'],
        'mac-arm64': ['chrome-headless-shell-mac-arm64', 'chrome-headless-shell'],
        'win-x64': ['chrome-headless-shell-win64', 'chrome-headless-shell.exe'],
    },
    'chromium-tip-of-tree': {
        '<unknown>': undefined,
        'linux-x64': ['chrome-linux64', 'chrome'],
        'linux-arm64': ['chrome-linux', 'chrome'], // non-cft build
        'mac-x64': ['chrome-mac-x64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'],
        'mac-arm64': ['chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'],
        'win-x64': ['chrome-win64', 'chrome.exe'],
    },
    'chromium-tip-of-tree-headless-shell': {
        '<unknown>': undefined,
        'linux-x64': ['chrome-headless-shell-linux64', 'chrome-headless-shell'],
        'linux-arm64': ['chrome-linux', 'headless_shell'], // non-cft build
        'mac-x64': ['chrome-headless-shell-mac-x64', 'chrome-headless-shell'],
        'mac-arm64': ['chrome-headless-shell-mac-arm64', 'chrome-headless-shell'],
        'win-x64': ['chrome-headless-shell-win64', 'chrome-headless-shell.exe'],
    },
    'firefox': {
        '<unknown>': undefined,
        'linux-x64': ['firefox', 'firefox'],
        'linux-arm64': ['firefox', 'firefox'],
        'mac-x64': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
        'mac-arm64': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
        'win-x64': ['firefox', 'firefox.exe'],
    },
    'webkit': {
        '<unknown>': undefined,
        'linux-x64': ['pw_run.sh'],
        'linux-arm64': ['pw_run.sh'],
        'mac-x64': ['pw_run.sh'],
        'mac-arm64': ['pw_run.sh'],
        'win-x64': ['Playwright.exe'],
    },
    'ffmpeg': {
        '<unknown>': undefined,
        'linux-x64': ['ffmpeg-linux'],
        'linux-arm64': ['ffmpeg-linux'],
        'mac-x64': ['ffmpeg-mac'],
        'mac-arm64': ['ffmpeg-mac'],
        'win-x64': ['ffmpeg-win64.exe'],
    },
    'winldd': {
        '<unknown>': undefined,
        'linux-x64': undefined,
        'linux-arm64': undefined,
        'mac-x64': undefined,
        'mac-arm64': undefined,
        'win-x64': ['PrintDeps.exe'],
    },
};
function cftUrl(suffix) {
    return ({ browserVersion }) => {
        return {
            path: `builds/cft/${browserVersion}/${suffix}`,
            mirrors: [
                'https://cdn.playwright.dev',
            ],
        };
    };
}
const DOWNLOAD_PATHS = {
    'chromium': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu22.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu24.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'ubuntu24.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'debian11-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian11-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'debian12-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian12-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'debian13-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian13-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
        'mac10.13': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac10.14': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac10.15': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac11': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac11-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac12': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac12-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac13': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac13-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac14': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac14-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac15': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac15-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac26': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac26-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'win64': cftUrl('win64/chrome-win64.zip'),
    },
    'chromium-headless-shell': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu22.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu24.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'ubuntu24.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'debian11-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian11-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'debian12-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian12-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'debian13-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian13-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
        'mac10.13': undefined,
        'mac10.14': undefined,
        'mac10.15': undefined,
        'mac11': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac11-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac12': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac12-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac13': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac13-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac14': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac14-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac15': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac15-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac26': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac26-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'win64': cftUrl('win64/chrome-headless-shell-win64.zip'),
    },
    'chromium-tip-of-tree': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu22.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu24.04-x64': cftUrl('linux64/chrome-linux64.zip'),
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'ubuntu24.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'debian11-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'debian12-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'debian13-x64': cftUrl('linux64/chrome-linux64.zip'),
        'debian13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
        'mac10.13': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac10.14': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac10.15': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac11': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac11-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac12': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac12-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac13': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac13-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac14': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac14-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac15': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac15-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'mac26': cftUrl('mac-x64/chrome-mac-x64.zip'),
        'mac26-arm64': cftUrl('mac-arm64/chrome-mac-arm64.zip'),
        'win64': cftUrl('win64/chrome-win64.zip'),
    },
    'chromium-tip-of-tree-headless-shell': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu22.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu24.04-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'ubuntu24.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'debian11-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'debian12-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'debian13-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
        'debian13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
        'mac10.13': undefined,
        'mac10.14': undefined,
        'mac10.15': undefined,
        'mac11': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac11-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac12': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac12-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac13': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac13-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac14': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac14-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac15': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac15-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'mac26': cftUrl('mac-x64/chrome-headless-shell-mac-x64.zip'),
        'mac26-arm64': cftUrl('mac-arm64/chrome-headless-shell-mac-arm64.zip'),
        'win64': cftUrl('win64/chrome-headless-shell-win64.zip'),
    },
    'firefox': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': 'builds/firefox/%s/firefox-ubuntu-20.04.zip',
        'ubuntu22.04-x64': 'builds/firefox/%s/firefox-ubuntu-22.04.zip',
        'ubuntu24.04-x64': 'builds/firefox/%s/firefox-ubuntu-24.04.zip',
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip',
        'ubuntu22.04-arm64': 'builds/firefox/%s/firefox-ubuntu-22.04-arm64.zip',
        'ubuntu24.04-arm64': 'builds/firefox/%s/firefox-ubuntu-24.04-arm64.zip',
        'debian11-x64': 'builds/firefox/%s/firefox-debian-11.zip',
        'debian11-arm64': 'builds/firefox/%s/firefox-debian-11-arm64.zip',
        'debian12-x64': 'builds/firefox/%s/firefox-debian-12.zip',
        'debian12-arm64': 'builds/firefox/%s/firefox-debian-12-arm64.zip',
        'debian13-x64': 'builds/firefox/%s/firefox-debian-13.zip',
        'debian13-arm64': 'builds/firefox/%s/firefox-debian-13-arm64.zip',
        'mac10.13': 'builds/firefox/%s/firefox-mac.zip',
        'mac10.14': 'builds/firefox/%s/firefox-mac.zip',
        'mac10.15': 'builds/firefox/%s/firefox-mac.zip',
        'mac11': 'builds/firefox/%s/firefox-mac.zip',
        'mac11-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'mac12': 'builds/firefox/%s/firefox-mac.zip',
        'mac12-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'mac13': 'builds/firefox/%s/firefox-mac.zip',
        'mac13-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'mac14': 'builds/firefox/%s/firefox-mac.zip',
        'mac14-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'mac15': 'builds/firefox/%s/firefox-mac.zip',
        'mac15-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'mac26': 'builds/firefox/%s/firefox-mac.zip',
        'mac26-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
        'win64': 'builds/firefox/%s/firefox-win64.zip',
    },
    'firefox-beta': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip',
        'ubuntu22.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04.zip',
        'ubuntu24.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-24.04.zip',
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': undefined,
        'ubuntu22.04-arm64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04-arm64.zip',
        'ubuntu24.04-arm64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-24.04-arm64.zip',
        'debian11-x64': 'builds/firefox-beta/%s/firefox-beta-debian-11.zip',
        'debian11-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-11-arm64.zip',
        'debian12-x64': 'builds/firefox-beta/%s/firefox-beta-debian-12.zip',
        'debian12-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
        'debian13-x64': 'builds/firefox-beta/%s/firefox-beta-debian-12.zip',
        'debian13-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
        'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac11': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac11-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'mac12': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac12-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'mac13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac13-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'mac14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac14-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'mac15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac15-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'mac26': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
        'mac26-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
        'win64': 'builds/firefox-beta/%s/firefox-beta-win64.zip',
    },
    'webkit': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': 'builds/webkit/%s/webkit-ubuntu-20.04.zip',
        'ubuntu22.04-x64': 'builds/webkit/%s/webkit-ubuntu-22.04.zip',
        'ubuntu24.04-x64': 'builds/webkit/%s/webkit-ubuntu-24.04.zip',
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip',
        'ubuntu22.04-arm64': 'builds/webkit/%s/webkit-ubuntu-22.04-arm64.zip',
        'ubuntu24.04-arm64': 'builds/webkit/%s/webkit-ubuntu-24.04-arm64.zip',
        'debian11-x64': 'builds/webkit/%s/webkit-debian-11.zip',
        'debian11-arm64': 'builds/webkit/%s/webkit-debian-11-arm64.zip',
        'debian12-x64': 'builds/webkit/%s/webkit-debian-12.zip',
        'debian12-arm64': 'builds/webkit/%s/webkit-debian-12-arm64.zip',
        'debian13-x64': 'builds/webkit/%s/webkit-debian-13.zip',
        'debian13-arm64': 'builds/webkit/%s/webkit-debian-13-arm64.zip',
        'mac10.13': undefined,
        'mac10.14': undefined,
        'mac10.15': undefined,
        'mac11': undefined,
        'mac11-arm64': undefined,
        'mac12': undefined,
        'mac12-arm64': undefined,
        'mac13': undefined,
        'mac13-arm64': undefined,
        'mac14': 'builds/webkit/%s/webkit-mac-14.zip',
        'mac14-arm64': 'builds/webkit/%s/webkit-mac-14-arm64.zip',
        'mac15': 'builds/webkit/%s/webkit-mac-15.zip',
        'mac15-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
        'mac26': 'builds/webkit/%s/webkit-mac-15.zip',
        'mac26-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
        'win64': 'builds/webkit/%s/webkit-win64.zip',
    },
    'ffmpeg': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'ubuntu22.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'ubuntu24.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'ubuntu22.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'ubuntu24.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'debian11-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'debian11-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'debian12-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'debian12-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'debian13-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
        'debian13-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
        'mac10.13': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac10.14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac10.15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac11': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac11-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'mac12': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac12-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'mac13': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac13-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'mac14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac14-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'mac15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'mac26': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
        'mac26-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
        'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip',
    },
    'winldd': {
        '<unknown>': undefined,
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': undefined,
        'ubuntu22.04-x64': undefined,
        'ubuntu24.04-x64': undefined,
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': undefined,
        'ubuntu22.04-arm64': undefined,
        'ubuntu24.04-arm64': undefined,
        'debian11-x64': undefined,
        'debian11-arm64': undefined,
        'debian12-x64': undefined,
        'debian12-arm64': undefined,
        'debian13-x64': undefined,
        'debian13-arm64': undefined,
        'mac10.13': undefined,
        'mac10.14': undefined,
        'mac10.15': undefined,
        'mac11': undefined,
        'mac11-arm64': undefined,
        'mac12': undefined,
        'mac12-arm64': undefined,
        'mac13': undefined,
        'mac13-arm64': undefined,
        'mac14': undefined,
        'mac14-arm64': undefined,
        'mac15': undefined,
        'mac15-arm64': undefined,
        'mac26': undefined,
        'mac26-arm64': undefined,
        'win64': 'builds/winldd/%s/winldd-win64.zip',
    },
    'android': {
        '<unknown>': 'builds/android/%s/android.zip',
        'ubuntu18.04-x64': undefined,
        'ubuntu20.04-x64': 'builds/android/%s/android.zip',
        'ubuntu22.04-x64': 'builds/android/%s/android.zip',
        'ubuntu24.04-x64': 'builds/android/%s/android.zip',
        'ubuntu18.04-arm64': undefined,
        'ubuntu20.04-arm64': 'builds/android/%s/android.zip',
        'ubuntu22.04-arm64': 'builds/android/%s/android.zip',
        'ubuntu24.04-arm64': 'builds/android/%s/android.zip',
        'debian11-x64': 'builds/android/%s/android.zip',
        'debian11-arm64': 'builds/android/%s/android.zip',
        'debian12-x64': 'builds/android/%s/android.zip',
        'debian12-arm64': 'builds/android/%s/android.zip',
        'debian13-x64': 'builds/android/%s/android.zip',
        'debian13-arm64': 'builds/android/%s/android.zip',
        'mac10.13': 'builds/android/%s/android.zip',
        'mac10.14': 'builds/android/%s/android.zip',
        'mac10.15': 'builds/android/%s/android.zip',
        'mac11': 'builds/android/%s/android.zip',
        'mac11-arm64': 'builds/android/%s/android.zip',
        'mac12': 'builds/android/%s/android.zip',
        'mac12-arm64': 'builds/android/%s/android.zip',
        'mac13': 'builds/android/%s/android.zip',
        'mac13-arm64': 'builds/android/%s/android.zip',
        'mac14': 'builds/android/%s/android.zip',
        'mac14-arm64': 'builds/android/%s/android.zip',
        'mac15': 'builds/android/%s/android.zip',
        'mac15-arm64': 'builds/android/%s/android.zip',
        'mac26': 'builds/android/%s/android.zip',
        'mac26-arm64': 'builds/android/%s/android.zip',
        'win64': 'builds/android/%s/android.zip',
    },
};
export const defaultCacheDirectory = (() => {
    if (process.platform === 'linux')
        return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
    if (process.platform === 'darwin')
        return path.join(os.homedir(), 'Library', 'Caches');
    if (process.platform === 'win32')
        return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
    throw new Error('Unsupported platform: ' + process.platform);
})();
export const defaultRegistryDirectory = path.join(defaultCacheDirectory, 'ms-playwright');
export const registryDirectory = (() => {
    let result;
    const envDefined = getFromENV('PLAYWRIGHT_BROWSERS_PATH');
    if (envDefined === '0')
        result = path.join(packageRoot, '.local-browsers');
    else if (envDefined)
        result = envDefined;
    else
        result = defaultRegistryDirectory;
    if (!path.isAbsolute(result)) {
        // It is important to resolve to the absolute path:
        //   - for unzipping to work correctly;
        //   - so that registry directory matches between installation and execution.
        // INIT_CWD points to the root of `npm/yarn install` and is probably what
        // the user meant when typing the relative path.
        result = path.resolve(getFromENV('INIT_CWD') || process.cwd(), result);
    }
    return result;
})();
function isBrowserDirectory(browserDirectory) {
    const baseName = path.basename(browserDirectory);
    for (const browserName of allDownloadableDirectoriesThatEverExisted) {
        if (baseName.startsWith(browserName.replace(/-/g, '_') + '-'))
            return true;
    }
    return false;
}
function readDescriptors(browsersJSON) {
    return (browsersJSON['browsers']).map(obj => {
        const name = obj.name;
        const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
        const revision = revisionOverride || obj.revision;
        const browserDirectoryPrefix = revisionOverride ? `${name}_${hostPlatform}_special` : `${name}`;
        const descriptor = {
            name,
            revision,
            hasRevisionOverride: !!revisionOverride,
            // We only put browser version for the supported operating systems.
            browserVersion: revisionOverride ? undefined : obj.browserVersion,
            title: obj['title'],
            installByDefault: !!obj.installByDefault,
            // Method `isBrowserDirectory` determines directory to be browser iff
            // it starts with some browser name followed by '-'. Some browser names
            // are prefixes of others, e.g. 'webkit' is a prefix of `webkit-technology-preview`.
            // To avoid older registries erroneously removing 'webkit-technology-preview', we have to
            // ensure that browser folders to never include dashes inside.
            dir: path.join(registryDirectory, browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision),
        };
        return descriptor;
    });
}
const allDownloadableDirectoriesThatEverExisted = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell', 'chromium-tip-of-tree-headless-shell', 'winldd'];
const chromiumAliases = ['chrome-for-testing'];
export class Registry {
    _executables;
    constructor(browsersJSON) {
        const descriptors = readDescriptors(browsersJSON);
        const findExecutablePath = (dir, name) => {
            const tokens = EXECUTABLE_PATHS[name][shortPlatform];
            return tokens ? path.join(dir, ...tokens) : undefined;
        };
        const executablePathOrDie = (name, e, installByDefault, sdkLanguage) => {
            if (!e)
                throw new Error(`${name} is not supported on ${hostPlatform}`);
            const installCommand = buildPlaywrightCLICommand(sdkLanguage, `install${installByDefault ? '' : ' ' + name}`);
            if (!canAccessFile(e)) {
                const currentDockerVersion = readDockerVersionSync();
                const preferredDockerVersion = currentDockerVersion ? dockerVersion(currentDockerVersion.dockerImageNameTemplate) : null;
                const isOutdatedDockerImage = currentDockerVersion && preferredDockerVersion && currentDockerVersion.dockerImageName !== preferredDockerVersion.dockerImageName;
                const isFfmpeg = name === 'ffmpeg';
                let prettyMessage;
                if (isOutdatedDockerImage) {
                    prettyMessage = [
                        `Looks like Playwright was just updated to ${preferredDockerVersion.driverVersion}.`,
                        `Please update docker image as well.`,
                        `-  current: ${currentDockerVersion.dockerImageName}`,
                        `- required: ${preferredDockerVersion.dockerImageName}`,
                        ``,
                        `<3 Playwright Team`,
                    ].join('\n');
                }
                else if (isFfmpeg) {
                    prettyMessage = [
                        `Video rendering requires ffmpeg binary.`,
                        `Downloading it will not affect any of the system-wide settings.`,
                        `Please run the following command:`,
                        ``,
                        `    ${buildPlaywrightCLICommand(sdkLanguage, 'install ffmpeg')}`,
                        ``,
                        `<3 Playwright Team`,
                    ].join('\n');
                }
                else {
                    prettyMessage = [
                        `Looks like Playwright was just installed or updated.`,
                        `Please run the following command to download new browser${installByDefault ? 's' : ''}:`,
                        ``,
                        `    ${installCommand}`,
                        ``,
                        `<3 Playwright Team`,
                    ].join('\n');
                }
                throw new Error(`Executable doesn't exist at ${e}\n${wrapInASCIIBox(prettyMessage, 1)}`);
            }
            return e;
        };
        this._executables = [];
        const chromium = descriptors.find(d => d.name === 'chromium');
        const chromiumExecutable = findExecutablePath(chromium.dir, 'chromium');
        this._executables.push({
            name: 'chromium',
            browserName: 'chromium',
            directory: chromium.dir,
            executablePath: () => chromiumExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
            installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromium.dir, ['chrome-linux'], [], ['chrome-win']),
            downloadURLs: this._downloadURLs(chromium),
            title: chromium.title,
            revision: chromium.revision,
            browserVersion: chromium.browserVersion,
            _install: force => this._downloadExecutable(chromium, force, chromiumExecutable),
            _dependencyGroup: 'chromium',
            _isHermeticInstallation: true,
        });
        const chromiumHeadlessShell = descriptors.find(d => d.name === 'chromium-headless-shell');
        const chromiumHeadlessShellExecutable = findExecutablePath(chromiumHeadlessShell.dir, 'chromium-headless-shell');
        this._executables.push({
            name: 'chromium-headless-shell',
            browserName: 'chromium',
            directory: chromiumHeadlessShell.dir,
            executablePath: () => chromiumHeadlessShellExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('chromium', chromiumHeadlessShellExecutable, chromiumHeadlessShell.installByDefault, sdkLanguage),
            installType: chromiumHeadlessShell.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumHeadlessShell.dir, ['chrome-linux'], [], ['chrome-win']),
            downloadURLs: this._downloadURLs(chromiumHeadlessShell),
            title: chromiumHeadlessShell.title,
            revision: chromiumHeadlessShell.revision,
            browserVersion: chromiumHeadlessShell.browserVersion,
            _install: force => this._downloadExecutable(chromiumHeadlessShell, force, chromiumHeadlessShellExecutable),
            _dependencyGroup: 'chromium',
            _isHermeticInstallation: true,
        });
        const chromiumTipOfTreeHeadlessShell = descriptors.find(d => d.name === 'chromium-tip-of-tree-headless-shell');
        const chromiumTipOfTreeHeadlessShellExecutable = findExecutablePath(chromiumTipOfTreeHeadlessShell.dir, 'chromium-tip-of-tree-headless-shell');
        this._executables.push({
            name: 'chromium-tip-of-tree-headless-shell',
            browserName: 'chromium',
            directory: chromiumTipOfTreeHeadlessShell.dir,
            executablePath: () => chromiumTipOfTreeHeadlessShellExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('chromium', chromiumTipOfTreeHeadlessShellExecutable, chromiumTipOfTreeHeadlessShell.installByDefault, sdkLanguage),
            installType: chromiumTipOfTreeHeadlessShell.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTreeHeadlessShell.dir, ['chrome-linux'], [], ['chrome-win']),
            downloadURLs: this._downloadURLs(chromiumTipOfTreeHeadlessShell),
            title: chromiumTipOfTreeHeadlessShell.title,
            revision: chromiumTipOfTreeHeadlessShell.revision,
            browserVersion: chromiumTipOfTreeHeadlessShell.browserVersion,
            _install: force => this._downloadExecutable(chromiumTipOfTreeHeadlessShell, force, chromiumTipOfTreeHeadlessShellExecutable),
            _dependencyGroup: 'chromium',
            _isHermeticInstallation: true,
        });
        const chromiumTipOfTree = descriptors.find(d => d.name === 'chromium-tip-of-tree');
        const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, 'chromium-tip-of-tree');
        this._executables.push({
            name: 'chromium-tip-of-tree',
            browserName: 'chromium',
            directory: chromiumTipOfTree.dir,
            executablePath: () => chromiumTipOfTreeExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('chromium-tip-of-tree', chromiumTipOfTreeExecutable, chromiumTipOfTree.installByDefault, sdkLanguage),
            installType: chromiumTipOfTree.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTree.dir, ['chrome-linux'], [], ['chrome-win']),
            downloadURLs: this._downloadURLs(chromiumTipOfTree),
            title: chromiumTipOfTree.title,
            revision: chromiumTipOfTree.revision,
            browserVersion: chromiumTipOfTree.browserVersion,
            _install: force => this._downloadExecutable(chromiumTipOfTree, force, chromiumTipOfTreeExecutable),
            _dependencyGroup: 'chromium',
            _isHermeticInstallation: true,
        });
        this._executables.push(this._createChromiumChannel('chrome', {
            'linux': '/opt/google/chrome/chrome',
            'darwin': '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
            'win32': `\\Google\\Chrome\\Application\\chrome.exe`,
        }, () => this._installChromiumChannel('chrome', {
            'linux': 'reinstall_chrome_stable_linux.sh',
            'darwin': 'reinstall_chrome_stable_mac.sh',
            'win32': 'reinstall_chrome_stable_win.ps1',
        })));
        this._executables.push(this._createChromiumChannel('chrome-beta', {
            'linux': '/opt/google/chrome-beta/chrome',
            'darwin': '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
            'win32': `\\Google\\Chrome Beta\\Application\\chrome.exe`,
        }, () => this._installChromiumChannel('chrome-beta', {
            'linux': 'reinstall_chrome_beta_linux.sh',
            'darwin': 'reinstall_chrome_beta_mac.sh',
            'win32': 'reinstall_chrome_beta_win.ps1',
        })));
        this._executables.push(this._createChromiumChannel('chrome-dev', {
            'linux': '/opt/google/chrome-unstable/chrome',
            'darwin': '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
            'win32': `\\Google\\Chrome Dev\\Application\\chrome.exe`,
        }));
        this._executables.push(this._createChromiumChannel('chrome-canary', {
            'linux': '/opt/google/chrome-canary/chrome',
            'darwin': '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
            'win32': `\\Google\\Chrome SxS\\Application\\chrome.exe`,
        }));
        this._executables.push(this._createChromiumChannel('msedge', {
            'linux': '/opt/microsoft/msedge/msedge',
            'darwin': '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
            'win32': `\\Microsoft\\Edge\\Application\\msedge.exe`,
        }, () => this._installMSEdgeChannel('msedge', {
            'linux': 'reinstall_msedge_stable_linux.sh',
            'darwin': 'reinstall_msedge_stable_mac.sh',
            'win32': 'reinstall_msedge_stable_win.ps1',
        })));
        this._executables.push(this._createChromiumChannel('msedge-beta', {
            'linux': '/opt/microsoft/msedge-beta/msedge',
            'darwin': '/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta',
            'win32': `\\Microsoft\\Edge Beta\\Application\\msedge.exe`,
        }, () => this._installMSEdgeChannel('msedge-beta', {
            'darwin': 'reinstall_msedge_beta_mac.sh',
            'linux': 'reinstall_msedge_beta_linux.sh',
            'win32': 'reinstall_msedge_beta_win.ps1',
        })));
        this._executables.push(this._createChromiumChannel('msedge-dev', {
            'linux': '/opt/microsoft/msedge-dev/msedge',
            'darwin': '/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev',
            'win32': `\\Microsoft\\Edge Dev\\Application\\msedge.exe`,
        }, () => this._installMSEdgeChannel('msedge-dev', {
            'darwin': 'reinstall_msedge_dev_mac.sh',
            'linux': 'reinstall_msedge_dev_linux.sh',
            'win32': 'reinstall_msedge_dev_win.ps1',
        })));
        this._executables.push(this._createChromiumChannel('msedge-canary', {
            'linux': '',
            'darwin': '/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary',
            'win32': `\\Microsoft\\Edge SxS\\Application\\msedge.exe`,
        }));
        this._executables.push(this._createBidiFirefoxChannel('moz-firefox', {
            'linux': '/snap/bin/firefox',
            'darwin': '/Applications/Firefox.app/Contents/MacOS/firefox',
            'win32': '\\Mozilla Firefox\\firefox.exe',
        }));
        this._executables.push(this._createBidiFirefoxChannel('moz-firefox-beta', {
            'linux': '/opt/firefox-beta/firefox',
            'darwin': '/Applications/Firefox.app/Contents/MacOS/firefox',
            'win32': '\\Mozilla Firefox\\firefox.exe',
        }));
        this._executables.push(this._createBidiFirefoxChannel('moz-firefox-nightly', {
            'linux': '/opt/firefox-nightly/firefox',
            'darwin': '/Applications/Firefox Nightly.app/Contents/MacOS/firefox',
            'win32': '\\Mozilla Firefox\\firefox.exe',
        }));
        const firefox = descriptors.find(d => d.name === 'firefox');
        const firefoxExecutable = findExecutablePath(firefox.dir, 'firefox');
        this._executables.push({
            name: 'firefox',
            browserName: 'firefox',
            directory: firefox.dir,
            executablePath: () => firefoxExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('firefox', firefoxExecutable, firefox.installByDefault, sdkLanguage),
            installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, firefox.dir, ['firefox'], [], ['firefox']),
            downloadURLs: this._downloadURLs(firefox),
            title: firefox.title,
            revision: firefox.revision,
            browserVersion: firefox.browserVersion,
            _install: force => this._downloadExecutable(firefox, force, firefoxExecutable),
            _dependencyGroup: 'firefox',
            _isHermeticInstallation: true,
        });
        const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta');
        const firefoxBetaExecutable = findExecutablePath(firefoxBeta.dir, 'firefox');
        this._executables.push({
            name: 'firefox-beta',
            browserName: 'firefox',
            directory: firefoxBeta.dir,
            executablePath: () => firefoxBetaExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('firefox-beta', firefoxBetaExecutable, firefoxBeta.installByDefault, sdkLanguage),
            installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, firefoxBeta.dir, ['firefox'], [], ['firefox']),
            downloadURLs: this._downloadURLs(firefoxBeta),
            title: firefoxBeta.title,
            revision: firefoxBeta.revision,
            browserVersion: firefoxBeta.browserVersion,
            _install: force => this._downloadExecutable(firefoxBeta, force, firefoxBetaExecutable),
            _dependencyGroup: 'firefox',
            _isHermeticInstallation: true,
        });
        const webkit = descriptors.find(d => d.name === 'webkit');
        const webkitExecutable = findExecutablePath(webkit.dir, 'webkit');
        const webkitLinuxLddDirectories = [
            path.join('minibrowser-gtk'),
            path.join('minibrowser-gtk', 'bin'),
            path.join('minibrowser-gtk', 'lib'),
            path.join('minibrowser-gtk', 'sys', 'lib'),
            path.join('minibrowser-wpe'),
            path.join('minibrowser-wpe', 'bin'),
            path.join('minibrowser-wpe', 'lib'),
            path.join('minibrowser-wpe', 'sys', 'lib'),
        ];
        this._executables.push({
            name: 'webkit',
            browserName: 'webkit',
            directory: webkit.dir,
            executablePath: () => webkitExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
            installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
            downloadURLs: this._downloadURLs(webkit),
            title: webkit.title,
            revision: webkit.revision,
            browserVersion: webkit.browserVersion,
            _install: force => this._downloadExecutable(webkit, force, webkitExecutable),
            _dependencyGroup: 'webkit',
            _isHermeticInstallation: true,
        });
        this._executables.push({
            name: 'webkit-wsl',
            browserName: 'webkit',
            directory: webkit.dir,
            executablePath: () => webkitExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
            installType: 'download-on-demand',
            title: 'Webkit in WSL',
            _validateHostRequirements: (sdkLanguage) => Promise.resolve(),
            _isHermeticInstallation: true,
            _install: async () => {
                if (process.platform !== 'win32')
                    throw new Error(`WebKit via WSL is only supported on Windows`);
                const script = path.join(BIN_PATH, 'install_webkit_wsl.ps1');
                const { code } = await spawnAsync('powershell.exe', [
                    '-ExecutionPolicy', 'Bypass',
                    '-File', script,
                ], {
                    stdio: 'inherit',
                });
                if (code !== 0)
                    throw new Error(`Failed to install WebKit via WSL`);
            },
        });
        const ffmpeg = descriptors.find(d => d.name === 'ffmpeg');
        const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg');
        this._executables.push({
            name: 'ffmpeg',
            browserName: undefined,
            directory: ffmpeg.dir,
            executablePath: () => ffmpegExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('ffmpeg', ffmpegExecutable, ffmpeg.installByDefault, sdkLanguage),
            installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand',
            _validateHostRequirements: () => Promise.resolve(),
            downloadURLs: this._downloadURLs(ffmpeg),
            title: ffmpeg.title,
            revision: ffmpeg.revision,
            _install: force => this._downloadExecutable(ffmpeg, force, ffmpegExecutable),
            _dependencyGroup: 'tools',
            _isHermeticInstallation: true,
        });
        const winldd = descriptors.find(d => d.name === 'winldd');
        const winlddExecutable = findExecutablePath(winldd.dir, 'winldd');
        this._executables.push({
            name: 'winldd',
            browserName: undefined,
            directory: winldd.dir,
            executablePath: () => winlddExecutable,
            executablePathOrDie: (sdkLanguage) => executablePathOrDie('winldd', winlddExecutable, winldd.installByDefault, sdkLanguage),
            installType: process.platform === 'win32' ? 'download-by-default' : 'none',
            _validateHostRequirements: () => Promise.resolve(),
            downloadURLs: this._downloadURLs(winldd),
            title: winldd.title,
            revision: winldd.revision,
            _install: force => this._downloadExecutable(winldd, force, winlddExecutable),
            _dependencyGroup: 'tools',
            _isHermeticInstallation: true,
        });
        const android = descriptors.find(d => d.name === 'android');
        this._executables.push({
            name: 'android',
            browserName: undefined,
            directory: android.dir,
            executablePath: () => undefined,
            executablePathOrDie: () => '',
            installType: 'download-on-demand',
            _validateHostRequirements: () => Promise.resolve(),
            downloadURLs: this._downloadURLs(android),
            title: android.title,
            revision: android.revision,
            _install: force => this._downloadExecutable(android, force),
            _dependencyGroup: 'tools',
            _isHermeticInstallation: true,
        });
    }
    _createChromiumChannel(name, lookAt, install) {
        const executablePath = (sdkLanguage, shouldThrow) => {
            const suffix = lookAt[process.platform];
            if (!suffix) {
                if (shouldThrow)
                    throw new Error(`Chromium distribution '${name}' is not supported on ${process.platform}`);
                return undefined;
            }
            const prefixes = (process.platform === 'win32' ? [
                process.env.LOCALAPPDATA,
                process.env.PROGRAMFILES,
                process.env['PROGRAMFILES(X86)'],
                // In some cases there is no PROGRAMFILES/(86) env var set but HOMEDRIVE is set.
                process.env.HOMEDRIVE + '\\Program Files',
                process.env.HOMEDRIVE + '\\Program Files (x86)',
            ].filter(Boolean) : ['']);
            for (const prefix of prefixes) {
                const executablePath = path.join(prefix, suffix);
                if (canAccessFile(executablePath))
                    return executablePath;
            }
            if (!shouldThrow)
                return undefined;
            const location = prefixes.length ? ` at ${path.join(prefixes[0], suffix)}` : ``;
            const installation = install ? `\nRun "${buildPlaywrightCLICommand(sdkLanguage, 'install ' + name)}"` : '';
            throw new Error(`Chromium distribution '${name}' is not found${location}${installation}`);
        };
        return {
            name,
            browserName: 'chromium',
            directory: undefined,
            executablePath: () => executablePath('', false),
            executablePathOrDie: (sdkLanguage) => executablePath(sdkLanguage, true),
            installType: install ? 'install-script' : 'none',
            _validateHostRequirements: () => Promise.resolve(),
            _isHermeticInstallation: false,
            _install: install,
        };
    }
    _createBidiFirefoxChannel(name, lookAt, install) {
        const executablePath = (sdkLanguage, shouldThrow) => {
            const suffix = lookAt[process.platform];
            if (!suffix) {
                if (shouldThrow)
                    throw new Error(`Firefox distribution '${name}' is not supported on ${process.platform}`);
                return undefined;
            }
            const prefixes = (process.platform === 'win32' ? [
                process.env.LOCALAPPDATA,
                process.env.PROGRAMFILES,
                process.env['PROGRAMFILES(X86)'],
                // In some cases there is no PROGRAMFILES/(86) env var set but HOMEDRIVE is set.
                process.env.HOMEDRIVE + '\\Program Files',
                process.env.HOMEDRIVE + '\\Program Files (x86)',
            ].filter(Boolean) : ['']);
            for (const prefix of prefixes) {
                const executablePath = path.join(prefix, suffix);
                if (canAccessFile(executablePath))
                    return executablePath;
            }
            if (shouldThrow)
                throw new Error(`Cannot find Firefox installation for channel '${name}' at the standard system paths. ${`Tried paths:\n  ${prefixes.map(p => path.join(p, suffix)).join('\n  ')}`}`);
            return undefined;
        };
        return {
            name,
            browserName: 'firefox',
            directory: undefined,
            executablePath: () => executablePath('', false),
            executablePathOrDie: (sdkLanguage) => executablePath(sdkLanguage, true),
            installType: 'none',
            _validateHostRequirements: () => Promise.resolve(),
            _isHermeticInstallation: true,
            _install: install,
        };
    }
    executables() {
        return this._executables;
    }
    findExecutable(name) {
        return this._executables.find(b => b.name === name);
    }
    defaultExecutables() {
        return this._executables.filter(e => e.installType === 'download-by-default');
    }
    _dedupe(executables) {
        return Array.from(new Set(executables));
    }
    async _validateHostRequirements(sdkLanguage, browserDirectory, linuxLddDirectories, dlOpenLibraries, windowsExeAndDllDirectories) {
        if (os.platform() === 'linux')
            return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries);
        if (os.platform() === 'win32' && os.arch() === 'x64')
            return await validateDependenciesWindows(sdkLanguage, windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d)));
    }
    async installDeps(executablesToInstallDeps, dryRun) {
        const executables = this._dedupe(executablesToInstallDeps);
        const targets = new Set();
        for (const executable of executables) {
            if (executable._dependencyGroup)
                targets.add(executable._dependencyGroup);
        }
        targets.add('tools');
        if (os.platform() === 'win32')
            return await installDependenciesWindows(targets, dryRun);
        if (os.platform() === 'linux')
            return await installDependenciesLinux(targets, dryRun);
    }
    async install(executablesToInstall, options) {
        const executables = this._dedupe(executablesToInstall);
        await fs.promises.mkdir(registryDirectory, { recursive: true });
        const lockfilePath = path.join(registryDirectory, '__dirlock');
        const linksDir = path.join(registryDirectory, '.links');
        let releaseLock;
        try {
            releaseLock = await lock(registryDirectory, {
                retries: {
                    // Retry 20 times during 10 minutes with
                    // exponential back-off.
                    // See documentation at: https://www.npmjs.com/package/retry#retrytimeoutsoptions
                    retries: 20,
                    factor: 1.27579,
                },
                onCompromised: (err) => {
                    throw new Error(`${err.message} Path: ${lockfilePath}`);
                },
                lockfilePath,
            });
            // Create a link first, so that cache validation does not remove our own browsers.
            await fs.promises.mkdir(linksDir, { recursive: true });
            await fs.promises.writeFile(path.join(linksDir, calculateSha1(PACKAGE_PATH)), PACKAGE_PATH);
            // Remove stale browsers.
            if (!getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_GC'))
                await this._validateInstallationCache(linksDir);
            // Install browsers for this package.
            for (const executable of executables) {
                if (!executable._install)
                    throw new Error(`ERROR: Playwright does not support installing ${executable.name}`);
                if (!getAsBooleanFromENV('CI') && !executable._isHermeticInstallation && !options?.force && executable.executablePath()) {
                    const { embedderName } = getEmbedderName();
                    const command = buildPlaywrightCLICommand(embedderName, 'install --force ' + executable.name);
                    // eslint-disable-next-line no-restricted-properties
                    process.stderr.write('\n' + wrapInASCIIBox([
                        `ATTENTION: "${executable.name}" is already installed on the system!`,
                        ``,
                        `"${executable.name}" installation is not hermetic; installing newer version`,
                        `requires *removal* of a current installation first.`,
                        ``,
                        `To *uninstall* current version and re-install latest "${executable.name}":`,
                        ``,
                        `- Close all running instances of "${executable.name}", if any`,
                        `- Use "--force" to install browser:`,
                        ``,
                        `    ${command}`,
                        ``,
                        `<3 Playwright Team`,
                    ].join('\n'), 1) + '\n\n');
                    return;
                }
                await executable._install(!!options?.force);
            }
        }
        catch (e) {
            if (e.code === 'ELOCKED') {
                const rmCommand = process.platform === 'win32' ? 'rm -R' : 'rm -rf';
                throw new Error('\n' + wrapInASCIIBox([
                    `An active lockfile is found at:`,
                    ``,
                    `  ${lockfilePath}`,
                    ``,
                    `Either:`,
                    `- wait a few minutes if other Playwright is installing browsers in parallel`,
                    `- remove lock manually with:`,
                    ``,
                    `    ${rmCommand} ${lockfilePath}`,
                    ``,
                    `<3 Playwright Team`,
                ].join('\n'), 1));
            }
            else {
                throw e;
            }
        }
        finally {
            if (releaseLock)
                await releaseLock();
        }
    }
    async uninstall(all) {
        const linksDir = path.join(registryDirectory, '.links');
        if (all) {
            const links = await fs.promises.readdir(linksDir).catch(() => []);
            for (const link of links)
                await fs.promises.unlink(path.join(linksDir, link));
        }
        else {
            await fs.promises.unlink(path.join(linksDir, calculateSha1(PACKAGE_PATH))).catch(() => { });
        }
        // Remove stale browsers.
        await this._validateInstallationCache(linksDir);
        return {
            numberOfBrowsersLeft: (await fs.promises.readdir(registryDirectory).catch(() => [])).filter(browserDirectory => isBrowserDirectory(browserDirectory)).length
        };
    }
    async validateHostRequirementsForExecutablesIfNeeded(executables, sdkLanguage) {
        if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS')) {
            // eslint-disable-next-line no-restricted-properties
            process.stderr.write('Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.\n');
            return;
        }
        for (const executable of executables)
            await this._validateHostRequirementsForExecutableIfNeeded(executable, sdkLanguage);
    }
    async _validateHostRequirementsForExecutableIfNeeded(executable, sdkLanguage) {
        const kMaximumReValidationPeriod = 30 * 24 * 60 * 60 * 1000; // 30 days
        // Executable does not require validation.
        if (!executable.directory)
            return;
        const markerFile = path.join(executable.directory, 'DEPENDENCIES_VALIDATED');
        // Executable is already validated.
        if (await fs.promises.stat(markerFile).then(stat => (Date.now() - stat.mtime.getTime()) < kMaximumReValidationPeriod).catch(() => false))
            return;
        debugLogger.log('install', `validating host requirements for "${executable.name}"`);
        try {
            await executable._validateHostRequirements(sdkLanguage);
            debugLogger.log('install', `validation passed for ${executable.name}`);
        }
        catch (error) {
            debugLogger.log('install', `validation failed for ${executable.name}`);
            throw error;
        }
        await fs.promises.writeFile(markerFile, '').catch(() => { });
    }
    _downloadURLs(descriptor) {
        const paths = DOWNLOAD_PATHS[descriptor.name];
        const downloadPathTemplate = paths[hostPlatform] || paths['<unknown>'];
        if (!downloadPathTemplate)
            return [];
        let downloadPath;
        let mirrors;
        if (typeof downloadPathTemplate === 'function') {
            const result = downloadPathTemplate(descriptor);
            downloadPath = result.path;
            mirrors = result.mirrors;
        }
        else {
            downloadPath = util.format(downloadPathTemplate, descriptor.revision);
            mirrors = PLAYWRIGHT_CDN_MIRRORS;
        }
        let downloadHostEnv;
        if (descriptor.name.startsWith('chromium'))
            downloadHostEnv = 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST';
        else if (descriptor.name.startsWith('firefox'))
            downloadHostEnv = 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST';
        else if (descriptor.name.startsWith('webkit'))
            downloadHostEnv = 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST';
        const customHostOverride = (downloadHostEnv && getFromENV(downloadHostEnv)) || getFromENV('PLAYWRIGHT_DOWNLOAD_HOST');
        if (customHostOverride)
            mirrors = [customHostOverride];
        return mirrors.map(mirror => `${mirror}/${downloadPath}`);
    }
    async _downloadExecutable(descriptor, force, executablePath) {
        const downloadURLs = this._downloadURLs(descriptor);
        if (!downloadURLs.length)
            throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${hostPlatform}`);
        if (!isOfficiallySupportedPlatform)
            logPolitely(`BEWARE: your OS is not officially supported by Playwright; downloading fallback build for ${hostPlatform}.`);
        if (descriptor.hasRevisionOverride) {
            const message = `You are using a frozen ${descriptor.name} browser which does not receive updates anymore on ${hostPlatform}. Please update to the latest version of your operating system to test up-to-date browsers.`;
            if (process.env.GITHUB_ACTIONS)
                console.log(`::warning title=Playwright::${message}`); // eslint-disable-line no-console
            else
                logPolitely(message);
        }
        const title = this.calculateDownloadTitle(descriptor);
        const downloadFileName = `playwright-download-${descriptor.name}-${hostPlatform}-${descriptor.revision}.zip`;
        // PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT is a misnomer, it actually controls the socket's
        // max idle timeout. Unfortunately, we cannot rename it without breaking existing user workflows.
        const downloadSocketTimeoutEnv = getFromENV('PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT');
        const downloadSocketTimeout = +(downloadSocketTimeoutEnv || '0') || NET_DEFAULT_TIMEOUT;
        await downloadBrowserWithProgressBar(title, descriptor.dir, executablePath, downloadURLs, downloadFileName, downloadSocketTimeout, force).catch(e => {
            throw new Error(`Failed to download ${title}, caused by\n${e.stack}`);
        });
    }
    calculateDownloadTitle(descriptor) {
        const title = descriptor.title ?? descriptor.name.split('-').map(word => {
            return word === 'ffmpeg' ? 'FFmpeg' : word.charAt(0).toUpperCase() + word.slice(1);
        }).join(' ');
        const version = descriptor.browserVersion ? ' ' + descriptor.browserVersion : '';
        return `${title}${version} (playwright ${descriptor.name} v${descriptor.revision})`;
    }
    async _installMSEdgeChannel(channel, scripts) {
        const scriptArgs = [];
        if (process.platform !== 'linux') {
            const products = lowercaseAllKeys(JSON.parse(await fetchData(undefined, { url: 'https://edgeupdates.microsoft.com/api/products' })));
            const productName = {
                'msedge': 'Stable',
                'msedge-beta': 'Beta',
                'msedge-dev': 'Dev',
            }[channel];
            const product = products.find((product) => product.product === productName);
            const searchConfig = {
                darwin: { platform: 'MacOS', arch: 'universal', artifact: 'pkg' },
                win32: { platform: 'Windows', arch: 'x64', artifact: 'msi' },
            }[process.platform];
            const release = searchConfig ? product.releases.find((release) => release.platform === searchConfig.platform && release.architecture === searchConfig.arch && release.artifacts.length > 0) : null;
            const artifact = release ? release.artifacts.find((artifact) => artifact.artifactname === searchConfig.artifact) : null;
            if (artifact)
                scriptArgs.push(artifact.location /* url */);
            else
                throw new Error(`Cannot install ${channel} on ${process.platform}`);
        }
        await this._installChromiumChannel(channel, scripts, scriptArgs);
    }
    async _installChromiumChannel(channel, scripts, scriptArgs = []) {
        const scriptName = scripts[process.platform];
        if (!scriptName)
            throw new Error(`Cannot install ${channel} on ${process.platform}`);
        const cwd = BIN_PATH;
        const isPowerShell = scriptName.endsWith('.ps1');
        if (isPowerShell) {
            const args = [
                '-ExecutionPolicy', 'Bypass', '-File',
                path.join(BIN_PATH, scriptName),
                ...scriptArgs
            ];
            const { code } = await spawnAsync('powershell.exe', args, { cwd, stdio: 'inherit' });
            if (code !== 0)
                throw new Error(`Failed to install ${channel}`);
        }
        else {
            const shellArgs = scriptArgs.map(a => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
            const { command, args, elevatedPermissions } = await transformCommandsForRoot([`bash "${path.join(BIN_PATH, scriptName)}" ${shellArgs}`]);
            if (elevatedPermissions)
                console.log('Switching to root user to install dependencies...'); // eslint-disable-line no-console
            const { code } = await spawnAsync(command, args, { cwd, stdio: 'inherit' });
            if (code !== 0)
                throw new Error(`Failed to install ${channel}`);
        }
    }
    async listInstalledBrowsers() {
        const linksDir = path.join(registryDirectory, '.links');
        const { browsers } = await this._traverseBrowserInstallations(linksDir);
        return browsers.filter(browser => fs.existsSync(browser.browserPath));
    }
    async _validateInstallationCache(linksDir) {
        const { browsers, brokenLinks } = await this._traverseBrowserInstallations(linksDir);
        await this._deleteStaleBrowsers(browsers);
        await this._deleteBrokenInstallations(brokenLinks);
    }
    async _traverseBrowserInstallations(linksDir) {
        const browserList = [];
        const brokenLinks = [];
        for (const fileName of await fs.promises.readdir(linksDir)) {
            const linkPath = path.join(linksDir, fileName);
            let linkTarget = '';
            try {
                linkTarget = (await fs.promises.readFile(linkPath)).toString();
                const browsersJSON = require(path.join(linkTarget, 'browsers.json'));
                const descriptors = readDescriptors(browsersJSON);
                for (const browserName of allDownloadableDirectoriesThatEverExisted) {
                    // We retain browsers if they are found in the descriptor.
                    // Note, however, that there are older versions out in the wild that rely on
                    // the "download" field in the browser descriptor and use its value
                    // to retain and download browsers.
                    // As of v1.10, we decided to abandon "download" field.
                    const descriptor = descriptors.find(d => d.name === browserName);
                    if (!descriptor)
                        continue;
                    const browserPath = descriptor.dir;
                    const browserVersion = parseInt(descriptor.revision, 10);
                    browserList.push({
                        browserName,
                        browserVersion,
                        browserPath,
                        referenceDir: linkTarget,
                    });
                }
            }
            catch (e) {
                brokenLinks.push(linkPath);
            }
        }
        return { browsers: browserList, brokenLinks };
    }
    async _deleteStaleBrowsers(browserList) {
        const usedBrowserPaths = new Set();
        for (const browser of browserList) {
            const { browserName, browserVersion, browserPath } = browser;
            // Old browser installations don't have marker file.
            // We switched chromium from 999999 to 1000, 300000 is the new Y2K.
            const shouldHaveMarkerFile = (browserName === 'chromium' && (browserVersion >= 786218 || browserVersion < 300000)) ||
                (browserName === 'firefox' && browserVersion >= 1128) ||
                (browserName === 'webkit' && browserVersion >= 1307) ||
                // All new applications have a marker file right away.
                (browserName !== 'firefox' && browserName !== 'chromium' && browserName !== 'webkit');
            if (!shouldHaveMarkerFile || (await existsAsync(browserDirectoryToMarkerFilePath(browserPath))))
                usedBrowserPaths.add(browserPath);
        }
        let downloadedBrowsers = (await fs.promises.readdir(registryDirectory)).map(file => path.join(registryDirectory, file));
        downloadedBrowsers = downloadedBrowsers.filter(file => isBrowserDirectory(file));
        const directories = new Set(downloadedBrowsers);
        for (const browserDirectory of usedBrowserPaths)
            directories.delete(browserDirectory);
        for (const directory of directories)
            logPolitely('Removing unused browser at ' + directory);
        await removeFolders([...directories]);
    }
    async _deleteBrokenInstallations(brokenLinks) {
        for (const linkPath of brokenLinks)
            await fs.promises.unlink(linkPath).catch(e => { });
    }
    _defaultBrowsersToInstall(options) {
        let executables = this.defaultExecutables();
        if (options.shell === 'no')
            executables = executables.filter(e => e.name !== 'chromium-headless-shell' && e.name !== 'chromium-tip-of-tree-headless-shell');
        if (options.shell === 'only')
            executables = executables.filter(e => e.name !== 'chromium' && e.name !== 'chromium-tip-of-tree');
        return executables;
    }
    suggestedBrowsersToInstall() {
        const names = this.executables().filter(e => e.installType !== 'none').map(e => e.name);
        names.push(...chromiumAliases);
        return names.sort().join(', ');
    }
    isChromiumAlias(name) {
        return chromiumAliases.includes(name);
    }
    resolveBrowsers(aliases, options) {
        if (aliases.length === 0)
            return this._defaultBrowsersToInstall(options);
        const faultyArguments = [];
        const executables = [];
        const handleArgument = (arg) => {
            const executable = this.findExecutable(arg);
            if (!executable || executable.installType === 'none')
                faultyArguments.push(arg);
            else
                executables.push(executable);
            if (executable?.browserName)
                executables.push(this.findExecutable('ffmpeg'));
        };
        for (const alias of aliases) {
            if (alias === 'chromium' || chromiumAliases.includes(alias)) {
                if (options.shell !== 'only')
                    handleArgument('chromium');
                if (options.shell !== 'no')
                    handleArgument('chromium-headless-shell');
            }
            else if (alias === 'chromium-tip-of-tree') {
                if (options.shell !== 'only')
                    handleArgument('chromium-tip-of-tree');
                if (options.shell !== 'no')
                    handleArgument('chromium-tip-of-tree-headless-shell');
            }
            else {
                handleArgument(alias);
            }
        }
        if (process.platform === 'win32')
            executables.push(this.findExecutable('winldd'));
        if (faultyArguments.length)
            throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${this.suggestedBrowsersToInstall()}`);
        return executables;
    }
}
export function browserDirectoryToMarkerFilePath(browserDirectory) {
    return path.join(browserDirectory, 'INSTALLATION_COMPLETE');
}
export function buildPlaywrightCLICommand(sdkLanguage, parameters) {
    switch (sdkLanguage) {
        case 'python':
            return `playwright ${parameters}`;
        case 'java':
            return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="${parameters}"`;
        case 'csharp':
            return `pwsh bin/Debug/netX/playwright.ps1 ${parameters}`;
        default: {
            const packageManagerCommand = getPackageManagerExecCommand();
            return `${packageManagerCommand} playwright ${parameters}`;
        }
    }
}
export async function installBrowsersForNpmInstall(browsers) {
    // PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD should have a value of 0 or 1
    if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD')) {
        logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
        return false;
    }
    const executables = [];
    if (process.platform === 'win32')
        executables.push(registry.findExecutable('winldd'));
    for (const browserName of browsers) {
        const executable = registry.findExecutable(browserName);
        if (!executable || executable.installType === 'none')
            throw new Error(`Cannot install ${browserName}`);
        executables.push(executable);
    }
    await registry.install(executables);
}
// for launchApp -> UI Mode / Trace Viewer
export function findChromiumChannelBestEffort(sdkLanguage) {
    // Fall back to the stable channels of popular vendors to work out of the box.
    // Null means no installation and no channels found.
    let channel = null;
    for (const name of ['chromium', 'chrome', 'msedge']) {
        try {
            registry.findExecutable(name).executablePathOrDie(sdkLanguage);
            channel = name === 'chromium' ? undefined : name;
            break;
        }
        catch (e) {
        }
    }
    if (channel === null) {
        const installCommand = buildPlaywrightCLICommand(sdkLanguage, `install chromium`);
        const prettyMessage = [
            `No chromium-based browser found on the system.`,
            `Please run the following command to download one:`,
            ``,
            `    ${installCommand}`,
            ``,
            `<3 Playwright Team`,
        ].join('\n');
        throw new Error('\n' + wrapInASCIIBox(prettyMessage, 1));
    }
    return channel;
}
function lowercaseAllKeys(json) {
    if (typeof json !== 'object' || !json)
        return json;
    if (Array.isArray(json))
        return json.map(lowercaseAllKeys);
    const result = {};
    for (const [key, value] of Object.entries(json))
        result[key.toLowerCase()] = lowercaseAllKeys(value);
    return result;
}
export const registry = new Registry(require(path.join(packageRoot, 'browsers.json')));
export { runOopDownloadBrowserMain } from './oopDownloadBrowserMain';
//# sourceMappingURL=index.js.map