chore: introduce @puppeteer/browsers with a fetch method implementation (#9647)

Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
This commit is contained in:
Alex Rudenko 2023-02-13 11:49:50 +01:00 committed by GitHub
parent 6ada628f39
commit b50e43bc17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1010 additions and 0 deletions

View File

@ -84,6 +84,9 @@ jobs:
ng-schematics:
- '.github/workflows/ci.yml'
- 'packages/ng-schematics/**'
browsers:
- '.github/workflows/ci.yml'
- 'packages/browsers/**'
deploy-docs:
needs: check-changes
@ -368,3 +371,38 @@ jobs:
run: npm ci --ignore-scripts
- name: Run tests
run: npm run test --workspace @puppeteer/ng-schematics
browsers-tests:
name: Browsers tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: check-changes
if: ${{ contains(fromJSON(needs.check-changes.outputs.changes), 'browsers') }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3.5.1
with:
cache: npm
node-version: latest
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run tests
run: npm run test --workspace @puppeteer/browsers
browsers-tests-required:
name: '[Required] Test the browsers packages'
needs: [check-changes, browsers-tests]
runs-on: ubuntu-latest
if: ${{ always() }}
steps:
- if: ${{ needs.browsers-tests.result != 'success' && contains(fromJSON(needs.check-changes.outputs.changes), 'browsers') }}
run: 'exit 1'
- run: 'exit 0'

57
package-lock.json generated
View File

@ -1385,6 +1385,10 @@
"resolved": "test",
"link": true
},
"node_modules/@puppeteer/browsers": {
"resolved": "packages/browsers",
"link": true
},
"node_modules/@puppeteer/ng-schematics": {
"resolved": "packages/ng-schematics",
"link": true
@ -8678,6 +8682,39 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"packages/browsers": {
"name": "@puppeteer/browsers",
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
},
"devDependencies": {
"@types/node": "^14.15.0"
},
"engines": {
"node": ">=14.1.0"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"packages/browsers/node_modules/@types/node": {
"version": "14.18.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
},
"packages/ng-schematics": {
"name": "@puppeteer/ng-schematics",
"version": "0.1.0",
@ -10040,6 +10077,26 @@
}
}
},
"@puppeteer/browsers": {
"version": "file:packages/browsers",
"requires": {
"@types/node": "^14.15.0",
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
},
"dependencies": {
"@types/node": {
"version": "14.18.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
}
}
},
"@puppeteer/ng-schematics": {
"version": "file:packages/ng-schematics",
"requires": {

View File

@ -0,0 +1,6 @@
module.exports = {
logLevel: 'debug',
spec: 'test/build/**/*.spec.js',
exit: !!process.env.CI,
reporter: 'spec',
};

View File

@ -0,0 +1,3 @@
# @puppeteer/browsers
TODO

View File

@ -0,0 +1,81 @@
{
"name": "@puppeteer/browsers",
"version": "0.0.1",
"description": "Download and launch browsers",
"scripts": {
"build": "wireit",
"build:test": "wireit",
"clean": "tsc --build --clean && rimraf lib",
"test": "wireit"
},
"wireit": {
"build": {
"command": "tsc -b",
"files": [
"src/**/*.ts",
"tsconfig.json"
],
"output": [
"lib/**"
]
},
"build:test": {
"command": "tsc -b test/src/tsconfig.json",
"files": [
"test/**/*.ts",
"test/src/tsconfig.json"
],
"output": [
"test/build/**"
],
"dependencies": [
"build"
]
},
"test": {
"command": "mocha",
"files": [
".mocharc.cjs"
],
"dependencies": [
"build:test"
]
}
},
"keywords": [
"puppeteer",
"browsers"
],
"repository": {
"type": "git",
"url": "https://github.com/puppeteer/puppeteer/tree/main/packages/browsers"
},
"author": "The Chromium Authors",
"license": "Apache-2.0",
"engines": {
"node": ">=14.1.0"
},
"files": [
"lib",
"!*.tsbuildinfo"
],
"dependencies": {
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
},
"devDependencies": {
"@types/node": "^14.15.0"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 * as chrome from './chrome.js';
import * as firefox from './firefox.js';
import {Browser, BrowserPlatform} from './types.js';
export const downloadUrls = {
[Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
};
export {Browser, BrowserPlatform};

View File

@ -0,0 +1,57 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 {BrowserPlatform} from './types.js';
function archive(platform: BrowserPlatform, revision: string): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'chrome-linux';
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.MAC:
return 'chrome-mac';
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
}
}
function folder(platform: BrowserPlatform): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'Linux_x64';
case BrowserPlatform.MAC_ARM:
return 'Mac_Arm';
case BrowserPlatform.MAC:
return 'Mac';
case BrowserPlatform.WIN32:
return 'Win';
case BrowserPlatform.WIN64:
return 'Win_x64';
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
revision: string,
baseUrl = 'https://storage.googleapis.com/chromium-browser-snapshots'
): string {
return `${baseUrl}/${folder(platform)}/${revision}/${archive(
platform,
revision
)}.zip`;
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 {BrowserPlatform} from './types.js';
function archive(platform: BrowserPlatform, revision: string): string {
switch (platform) {
case BrowserPlatform.LINUX:
return `firefox-${revision}.en-US.${platform}-x86_64.tar.bz2`;
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.MAC:
return `firefox-${revision}.en-US.mac.dmg`;
case BrowserPlatform.WIN32:
return `firefox-${revision}.en-US.${platform}.zip`;
case BrowserPlatform.WIN64:
return `firefox-${revision}.en-US.${platform}.zip`;
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
revision: string,
baseUrl = 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central'
): string {
return `${baseUrl}/${archive(platform, revision)}`;
}

View File

@ -0,0 +1,43 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 * as chrome from './chrome.js';
import * as firefox from './firefox.js';
/**
* Supported browsers.
*/
export enum Browser {
CHROME = 'chrome',
FIREFOX = 'firefox',
}
/**
* Platform names used to identify a OS platfrom x architecture combination in the way
* that is relevant for the browser download.
*/
export enum BrowserPlatform {
LINUX = 'linux',
MAC = 'mac',
MAC_ARM = 'mac_arm',
WIN32 = 'win32',
WIN64 = 'win64',
}
export const downloadUrls = {
[Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
};

View File

@ -0,0 +1,19 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 debug from 'debug';
export {debug};

View File

@ -0,0 +1,171 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* 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 {existsSync} from 'fs';
import {mkdir, unlink} from 'fs/promises';
import os from 'os';
import path from 'path';
import {debug} from './debug.js';
import {Browser, BrowserPlatform, downloadUrls} from './browsers/browsers.js';
import {downloadFile, headHttpRequest} from './httpUtil.js';
import assert from 'assert';
import {unpackArchive} from './fileUtil.js';
const debugFetch = debug('puppeteer:browsers:fetcher');
/**
* @public
*/
export interface Options {
/**
* Determines the path to download browsers to.
*/
outputDir: string;
/**
* Determines which platform the browser will be suited for.
*
* @defaultValue Auto-detected.
*/
platform?: BrowserPlatform;
/**
* Determines which browser to fetch.
*/
browser: Browser;
/**
* Determines which revision to dowloand. Revision should uniquely identify
* binaries and they are used for caching.
*/
revision: string;
/**
* Provides information about the progress of the download.
*/
progressCallback?: (downloadedBytes: number, totalBytes: number) => void;
}
export type InstalledBrowser = {
path: string;
browser: Browser;
revision: string;
platform: BrowserPlatform;
};
export async function fetch(options: Options): Promise<InstalledBrowser> {
options.platform ??= detectPlatform();
if (!options.platform) {
throw new Error(
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
const url = getDownloadUrl(
options.browser,
options.platform,
options.revision
);
const fileName = url.toString().split('/').pop();
assert(fileName, `A malformed download URL was found: ${url}.`);
const archivePath = path.join(options.outputDir, fileName);
const outputPath = path.resolve(
options.outputDir,
`${options.platform}-${options.revision}`
);
if (existsSync(outputPath)) {
return {
path: outputPath,
browser: options.browser,
platform: options.platform,
revision: options.revision,
};
}
if (!existsSync(options.outputDir)) {
await mkdir(options.outputDir, {recursive: true});
}
try {
debugFetch(`Downloading binary from ${url}`);
await downloadFile(url, archivePath, options.progressCallback);
debugFetch(`Installing ${archivePath} to ${outputPath}`);
await unpackArchive(archivePath, outputPath);
} finally {
if (existsSync(archivePath)) {
await unlink(archivePath);
}
}
return {
path: outputPath,
browser: options.browser,
platform: options.platform,
revision: options.revision,
};
}
export async function canFetch(options: Options): Promise<boolean> {
options.platform ??= detectPlatform();
if (!options.platform) {
throw new Error(
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
return await headHttpRequest(
getDownloadUrl(options.browser, options.platform, options.revision)
);
}
/**
* Windows 11 is identified by the version 10.0.22000 or greater
* @internal
*/
function isWindows11(version: string): boolean {
const parts = version.split('.');
if (parts.length > 2) {
const major = parseInt(parts[0] as string, 10);
const minor = parseInt(parts[1] as string, 10);
const patch = parseInt(parts[2] as string, 10);
return (
major > 10 ||
(major === 10 && minor > 0) ||
(major === 10 && minor === 0 && patch >= 22000)
);
}
return false;
}
function detectPlatform(): BrowserPlatform | undefined {
const platform = os.platform();
switch (platform) {
case 'darwin':
return os.arch() === 'arm64'
? BrowserPlatform.MAC_ARM
: BrowserPlatform.MAC;
case 'linux':
return BrowserPlatform.LINUX;
case 'win32':
return os.arch() === 'x64' ||
// Windows 11 for ARM supports x64 emulation
(os.arch() === 'arm64' && isWindows11(os.release()))
? BrowserPlatform.WIN64
: BrowserPlatform.WIN32;
default:
return undefined;
}
}
function getDownloadUrl(
browser: Browser,
platform: BrowserPlatform,
revision: string
): URL {
return new URL(downloadUrls[browser](platform, revision));
}

View File

@ -0,0 +1,88 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 * as path from 'path';
import {exec as execChildProcess} from 'child_process';
import extractZip from 'extract-zip';
import {createReadStream} from 'fs';
import {mkdir, readdir} from 'fs/promises';
import tar from 'tar-fs';
import bzip from 'unbzip2-stream';
import {promisify} from 'util';
const exec = promisify(execChildProcess);
/**
* @internal
*/
export async function unpackArchive(
archivePath: string,
folderPath: string
): Promise<void> {
if (archivePath.endsWith('.zip')) {
await extractZip(archivePath, {dir: folderPath});
} else if (archivePath.endsWith('.tar.bz2')) {
await extractTar(archivePath, folderPath);
} else if (archivePath.endsWith('.dmg')) {
await mkdir(folderPath);
await installDMG(archivePath, folderPath);
} else {
throw new Error(`Unsupported archive format: ${archivePath}`);
}
}
/**
* @internal
*/
function extractTar(tarPath: string, folderPath: string): Promise<void> {
return new Promise((fulfill, reject) => {
const tarStream = tar.extract(folderPath);
tarStream.on('error', reject);
tarStream.on('finish', fulfill);
const readStream = createReadStream(tarPath);
readStream.pipe(bzip()).pipe(tarStream);
});
}
/**
* @internal
*/
async function installDMG(dmgPath: string, folderPath: string): Promise<void> {
const {stdout} = await exec(
`hdiutil attach -nobrowse -noautoopen "${dmgPath}"`
);
const volumes = stdout.match(/\/Volumes\/(.*)/m);
if (!volumes) {
throw new Error(`Could not find volume path in ${stdout}`);
}
const mountPath = volumes[0]!;
try {
const fileNames = await readdir(mountPath);
const appName = fileNames.find(item => {
return typeof item === 'string' && item.endsWith('.app');
});
if (!appName) {
throw new Error(`Cannot find app in ${mountPath}`);
}
const mountedPath = path.join(mountPath!, appName);
await exec(`cp -R "${mountedPath}" "${folderPath}"`);
} finally {
await exec(`hdiutil detach "${mountPath}" -quiet`);
}
}

View File

@ -0,0 +1,136 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 * as http from 'http';
import * as https from 'https';
import {URL} from 'url';
import createHttpsProxyAgent from 'https-proxy-agent';
import {getProxyForUrl} from 'proxy-from-env';
import {createWriteStream} from 'fs';
export function headHttpRequest(url: URL): Promise<boolean> {
return new Promise(resolve => {
const request = httpRequest(
url,
'HEAD',
response => {
resolve(response.statusCode === 200);
},
false
);
request.on('error', () => {
resolve(false);
});
});
}
export function httpRequest(
url: URL,
method: string,
response: (x: http.IncomingMessage) => void,
keepAlive = true
): http.ClientRequest {
const options: http.RequestOptions = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port,
path: url.pathname,
headers: keepAlive ? {Connection: 'keep-alive'} : undefined,
};
const proxyURL = getProxyForUrl(url.toString());
if (proxyURL) {
const proxy = new URL(proxyURL);
if (proxy.protocol === 'http:') {
options.path = url.href;
options.hostname = proxy.hostname;
options.protocol = proxy.protocol;
} else {
options.agent = createHttpsProxyAgent({
host: proxy.host,
path: proxy.pathname,
port: proxy.port,
secureProxy: proxy.protocol === 'https:',
headers: options.headers,
});
}
}
const requestCallback = (res: http.IncomingMessage): void => {
if (
res.statusCode &&
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
httpRequest(new URL(res.headers.location), method, response);
} else {
response(res);
}
};
const request =
options.protocol === 'https:'
? https.request(options, requestCallback)
: http.request(options, requestCallback);
request.end();
return request;
}
/**
* @internal
*/
export function downloadFile(
url: URL,
destinationPath: string,
progressCallback?: (downloadedBytes: number, totalBytes: number) => void
): Promise<void> {
return new Promise<void>((resolve, reject) => {
let downloadedBytes = 0;
let totalBytes = 0;
function onData(chunk: string): void {
downloadedBytes += chunk.length;
progressCallback!(downloadedBytes, totalBytes);
}
const request = httpRequest(url, 'GET', response => {
if (response.statusCode !== 200) {
const error = new Error(
`Download failed: server returned code ${response.statusCode}. URL: ${url}`
);
// consume response data to free up memory
response.resume();
reject(error);
return;
}
const file = createWriteStream(destinationPath);
file.on('finish', () => {
return resolve();
});
file.on('error', error => {
return reject(error);
});
response.pipe(file);
totalBytes = parseInt(response.headers['content-length']!, 10);
if (progressCallback) {
response.on('data', onData);
}
});
request.on('error', error => {
return reject(error);
});
});
}

View File

@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "../lib/cjs"
}
}

View File

@ -0,0 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../lib/esm"
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 {resolveDownloadUrl} from '../../lib/cjs/browsers/chrome.js';
import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import assert from 'assert';
describe('Chrome', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Mac/1083080/chrome-mac.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Mac_Arm/1083080/chrome-mac.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Win/1083080/chrome-win.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1083080/chrome-win.zip'
);
});
});

View File

@ -0,0 +1,129 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 {fetch, canFetch} from '../../lib/cjs/fetch.js';
import {Browser, BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
import assert from 'assert';
/**
* Tests in this spec use real download URLs and unpack live browser archives
* so it requires the network access.
*/
describe('fetch', () => {
let tmpDir = '/tmp/puppeteer-browsers-test';
const testChromeRevision = '1083080';
const testFirefoxRevision = '111.0a1';
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should check if a revision can be downloaded', async () => {
assert.ok(
await canFetch({
outputDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
})
);
});
it('should report if a revision is not downloadable', async () => {
assert.strictEqual(
await canFetch({
outputDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: 'unknown',
}),
false
);
});
it('should download a revision that is a zip archive', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
`${BrowserPlatform.LINUX}-${testChromeRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
let browser = await fetch({
outputDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
// Second iteration should be no-op.
browser = await fetch({
outputDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
});
it('should download a revision that is a bzip2 archive', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
`${BrowserPlatform.LINUX}-${testFirefoxRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
const browser = await fetch({
outputDir: tmpDir,
browser: Browser.FIREFOX,
platform: BrowserPlatform.LINUX,
revision: testFirefoxRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
});
// Fetch relies on the `hdiutil` utility on MacOS.
// The utility is not available on other platforms.
(os.platform() === 'darwin' ? it : it.skip)(
'should download a revision that is a dmg archive',
async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
`${BrowserPlatform.MAC}-${testFirefoxRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
const browser = await fetch({
outputDir: tmpDir,
browser: Browser.FIREFOX,
platform: BrowserPlatform.MAC,
revision: testFirefoxRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
}
);
});

View File

@ -0,0 +1,44 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* 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 {resolveDownloadUrl} from '../../lib/cjs/browsers/firefox.js';
import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import assert from 'assert';
describe('Firefox', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win32.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win64.zip'
);
});
});

View File

@ -0,0 +1,8 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "../build"
},
"references": [{"path": "../../tsconfig.json"}]
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"references": [
{"path": "src/tsconfig.esm.json"},
{"path": "src/tsconfig.cjs.json"}
]
}