mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
183 lines
7.1 KiB
JavaScript
183 lines
7.1 KiB
JavaScript
![]() |
#!/usr/bin/env node
|
||
|
|
||
|
import {spawn} from 'child_process';
|
||
|
import * as path from 'path';
|
||
|
import * as URL from 'url';
|
||
|
import * as fs from 'fs';
|
||
|
import * as http from 'http';
|
||
|
import * as https from 'https';
|
||
|
|
||
|
const existsAsync = path => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||
|
|
||
|
const __filename = URL.fileURLToPath(import.meta.url);
|
||
|
const __dirname = path.dirname(__filename);
|
||
|
|
||
|
// Path to jar.mn in the juggler
|
||
|
const JARMN_PATH = path.join(__dirname, 'juggler', 'jar.mn');
|
||
|
// Workdir for Firefox repackaging
|
||
|
const BUILD_DIRECTORY = `/tmp/repackaged-firefox`;
|
||
|
// Information about currently downloaded build
|
||
|
const BUILD_INFO_PATH = path.join(BUILD_DIRECTORY, 'build-info.json');
|
||
|
// Backup OMNI.JA - the original one before repackaging.
|
||
|
const OMNI_BACKUP_PATH = path.join(BUILD_DIRECTORY, 'omni.ja.backup');
|
||
|
// Workdir to extract omni.ja
|
||
|
const OMNI_EXTRACT_DIR = path.join(BUILD_DIRECTORY, 'omni');
|
||
|
// Path inside omni.ja to juggler
|
||
|
const OMNI_JUGGLER_DIR = path.join(OMNI_EXTRACT_DIR, 'chrome', 'juggler');
|
||
|
|
||
|
const EXECUTABLE_PATHS = {
|
||
|
'ubuntu18.04': ['firefox', 'firefox'],
|
||
|
'ubuntu20.04': ['firefox', 'firefox'],
|
||
|
'mac10.13': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||
|
'mac10.14': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||
|
'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||
|
'mac11': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||
|
'mac11-arm64': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||
|
'win32': ['firefox', 'firefox.exe'],
|
||
|
'win64': ['firefox', 'firefox.exe'],
|
||
|
};
|
||
|
|
||
|
const buildNumber = (await fs.promises.readFile(path.join(__dirname, 'BUILD_NUMBER'), 'utf8')).split('\n').shift();
|
||
|
const expectedBuilds = (await fs.promises.readFile(path.join(__dirname, 'EXPECTED_BUILDS'), 'utf8')).split('\n');
|
||
|
|
||
|
if (process.argv.length !== 3) {
|
||
|
console.error(`ERROR: pass build name to repackage - one of ${expectedBuilds.join(', ')}`);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const buildName = process.argv[2];
|
||
|
if (!expectedBuilds.includes(buildName)) {
|
||
|
console.error(`ERROR: expected argument to be one of ${expectedBuilds.join(', ')}, received - "${buildName}"`);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const currentBuildInfo = await fs.promises.readFile(BUILD_INFO_PATH).then(text => JSON.parse(text)).catch(e => ({ buildName: '', buildNumber: '' }));
|
||
|
|
||
|
if (currentBuildInfo.buildName !== buildName || currentBuildInfo.buildNumber !== buildNumber) {
|
||
|
await fs.promises.rm(BUILD_DIRECTORY, { recursive: true }).catch(e => {});
|
||
|
await fs.promises.mkdir(BUILD_DIRECTORY);
|
||
|
const buildZipPath = path.join(BUILD_DIRECTORY, 'firefox.zip');
|
||
|
console.log(`Downloading ${buildName} ${buildNumber} - it might take a few minutes`);
|
||
|
await downloadFile(`https://playwright.azureedge.net/builds/firefox/${buildNumber}/${buildName}`, buildZipPath);
|
||
|
await spawnAsync('unzip', [ buildZipPath ], {cwd: BUILD_DIRECTORY});
|
||
|
await fs.promises.writeFile(BUILD_INFO_PATH, JSON.stringify({ buildNumber, buildName }), 'utf8');
|
||
|
}
|
||
|
|
||
|
// Find all omni.ja files in the Firefox build.
|
||
|
const omniPaths = await spawnAsync('find', ['.', '-name', 'omni.ja'], {
|
||
|
cwd: BUILD_DIRECTORY,
|
||
|
}).then(({stdout}) => stdout.trim().split('\n').map(aPath => path.join(BUILD_DIRECTORY, aPath)));
|
||
|
|
||
|
// Iterate over all omni.ja files and find one that has juggler inside.
|
||
|
const omniWithJugglerPath = await (async () => {
|
||
|
for (const omniPath of omniPaths) {
|
||
|
const {stdout} = await spawnAsync('unzip', ['-Z1', omniPath], {cwd: BUILD_DIRECTORY});
|
||
|
if (stdout.includes('chrome/juggler'))
|
||
|
return omniPath;
|
||
|
}
|
||
|
return null;
|
||
|
})();
|
||
|
|
||
|
if (!omniWithJugglerPath) {
|
||
|
console.error('ERROR: did not find omni.ja file with baked in Juggler!');
|
||
|
process.exit(1);
|
||
|
} else {
|
||
|
if (!(await existsAsync(OMNI_BACKUP_PATH)))
|
||
|
await fs.promises.copyFile(omniWithJugglerPath, OMNI_BACKUP_PATH);
|
||
|
}
|
||
|
|
||
|
// Let's repackage omni folder!
|
||
|
await fs.promises.rm(OMNI_EXTRACT_DIR, { recursive: true }).catch(e => {});
|
||
|
await fs.promises.mkdir(OMNI_EXTRACT_DIR);
|
||
|
|
||
|
await spawnAsync('unzip', [OMNI_BACKUP_PATH], {cwd: OMNI_EXTRACT_DIR });
|
||
|
// Remove current juggler directory
|
||
|
await fs.promises.rm(OMNI_JUGGLER_DIR, { recursive: true });
|
||
|
// Repopulate with tip-of-tree juggler files
|
||
|
const jarmn = await fs.promises.readFile(JARMN_PATH, 'utf8');
|
||
|
const jarLines = jarmn.split('\n').map(line => line.trim()).filter(line => line.startsWith('content/') && line.endsWith(')'));
|
||
|
for (const line of jarLines) {
|
||
|
const tokens = line.split(/\s+/);
|
||
|
const toPath = path.join(OMNI_JUGGLER_DIR, tokens[0]);
|
||
|
const fromPath = path.join(__dirname, 'juggler', tokens[1].slice(1, -1));
|
||
|
await fs.promises.mkdir(path.dirname(toPath), { recursive: true});
|
||
|
await fs.promises.copyFile(fromPath, toPath);
|
||
|
}
|
||
|
|
||
|
await fs.promises.rm(omniWithJugglerPath);
|
||
|
await spawnAsync('zip', ['-0', '-qr9XD', omniWithJugglerPath, '.'], {cwd: OMNI_EXTRACT_DIR, stdio: 'inherit'});
|
||
|
|
||
|
// Output executable path to be used in test.
|
||
|
const buildPlatform = buildName.substring('firefox-'.length).slice(0, -'.zip'.length).replace('-', '');
|
||
|
console.log(`
|
||
|
buildName: ${buildName}
|
||
|
buildNumber: ${buildNumber}
|
||
|
executablePath: ${path.join(BUILD_DIRECTORY, ...EXECUTABLE_PATHS[buildPlatform])}
|
||
|
`);
|
||
|
|
||
|
|
||
|
function httpRequest(url, method, response) {
|
||
|
let options = URL.parse(url);
|
||
|
options.method = method;
|
||
|
|
||
|
const requestCallback = res => {
|
||
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
|
||
|
httpRequest(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;
|
||
|
}
|
||
|
|
||
|
function downloadFile(url, destinationPath, progressCallback) {
|
||
|
let fulfill = ({error}) => {};
|
||
|
let downloadedBytes = 0;
|
||
|
let totalBytes = 0;
|
||
|
|
||
|
const promise = new Promise(x => { fulfill = x; });
|
||
|
|
||
|
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();
|
||
|
fulfill({error});
|
||
|
return;
|
||
|
}
|
||
|
const file = fs.createWriteStream(destinationPath);
|
||
|
file.on('finish', () => fulfill({error: null}));
|
||
|
file.on('error', error => fulfill({error}));
|
||
|
response.pipe(file);
|
||
|
totalBytes = parseInt(response.headers['content-length'], 10);
|
||
|
if (progressCallback)
|
||
|
response.on('data', onData);
|
||
|
});
|
||
|
request.on('error', error => fulfill({error}));
|
||
|
return promise;
|
||
|
|
||
|
function onData(chunk) {
|
||
|
downloadedBytes += chunk.length;
|
||
|
progressCallback(downloadedBytes, totalBytes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function spawnAsync(cmd, args, options) {
|
||
|
// console.log(cmd, ...args, 'CWD:', options.cwd);
|
||
|
const process = spawn(cmd, args, options);
|
||
|
|
||
|
return new Promise(resolve => {
|
||
|
let stdout = '';
|
||
|
let stderr = '';
|
||
|
if (process.stdout)
|
||
|
process.stdout.on('data', data => stdout += data);
|
||
|
if (process.stderr)
|
||
|
process.stderr.on('data', data => stderr += data);
|
||
|
process.on('close', code => resolve({stdout, stderr, code}));
|
||
|
process.on('error', error => resolve({stdout, stderr, code: 0, error}));
|
||
|
});
|
||
|
}
|