Skip to content

Commit f5ecbff

Browse files
authored
devops: remake downloading logic (#1419)
This patch: - removes `browserType.downloadBrowserIfNeeded()` method. The method turned out to be ill-behaving and cannot not be used as we'd like to (see #1085) - adds a `browserType.setExecutablePath` method to set a browser exectuable. With this patch, we take the following approach towards managing browser downloads: - `playwright-core` doesn't download any browsers. In `playwright-core`, `playwright.chromium.executablePath()` returns `null` (same for firefox and webkit). - clients of `playwright-core` (e.g. `playwright` and others) download browsers one way or another. They can then configure `playwright` with executable paths and re-export the `playwright` object to their clients. - `playwright`, `playwright-firefox`, `playwright-chromium` and `playwright-webkit` download browsers. Once browsers are downloaded, their executable paths are saved to a `.downloaded-browsers.json` file. This file is read in `playwright/index.js` to configure browser executable paths and re-export the API. - special case is `install-from-github.js` that also cleans up old browsers.
1 parent 2af07ce commit f5ecbff

25 files changed

+339
-509
lines changed

.circleci/config.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ jobs:
1515
- save_cache:
1616
key: dependency-cache-{{ checksum "package.json" }}
1717
paths:
18-
- ./.local-chromium
19-
- ./.local-firefox
20-
- ./.local-webkit
18+
- ./.local-browsers
2119

2220
- run:
2321
command: |

.gitignore

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
/test/output-firefox
44
/test/output-webkit
55
/test/test-user-data-dir*
6-
/.local-chromium/
7-
/.local-firefox/
8-
/.local-webkit/
6+
/.local-browsers/
97
/.dev_profile*
108
.DS_Store
9+
.downloaded-browsers.json
1110
*.swp
1211
*.pyc
1312
.vscode

docs/api.md

+1-8
Original file line numberDiff line numberDiff line change
@@ -3645,7 +3645,6 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
36453645

36463646
<!-- GEN:toc -->
36473647
- [browserType.connect(options)](#browsertypeconnectoptions)
3648-
- [browserType.downloadBrowserIfNeeded([progress])](#browsertypedownloadbrowserifneededprogress)
36493648
- [browserType.executablePath()](#browsertypeexecutablepath)
36503649
- [browserType.launch([options])](#browsertypelaunchoptions)
36513650
- [browserType.launchPersistentContext(userDataDir, [options])](#browsertypelaunchpersistentcontextuserdatadir-options)
@@ -3661,14 +3660,8 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
36613660

36623661
This methods attaches Playwright to an existing browser instance.
36633662

3664-
#### browserType.downloadBrowserIfNeeded([progress])
3665-
- `progress` <[function]> If download is initiated, this function is called with two parameters: `downloadedBytes` and `totalBytes`.
3666-
- returns: <[Promise]> promise that resolves when browser is successfully downloaded.
3667-
3668-
Download browser binary if it is missing.
3669-
36703663
#### browserType.executablePath()
3671-
- returns: <[string]> A path where Playwright expects to find a bundled browser.
3664+
- returns: <[string]> A path where Playwright expects to find a bundled browser executable.
36723665

36733666
#### browserType.launch([options])
36743667
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:

docs/troubleshooting.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ done only once per host environment:
164164

165165
```bash
166166
# cd to the downloaded instance
167-
cd <project-dir-path>/node_modules/playwright/.local-chromium/linux-<revision>/chrome-linux/
167+
cd <project-dir-path>/node_modules/playwright/.local-browsers/chromium-<revision>/
168168
sudo chown root:root chrome_sandbox
169169
sudo chmod 4755 chrome_sandbox
170170
# copy sandbox executable to a shared location

download-browser.js

+24-15
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,39 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
const fs = require('fs');
17+
const browserFetcher = require('./lib/server/browserFetcher.js');
18+
const packageJSON = require('./package.json');
1619

17-
async function downloadBrowser(browserType) {
18-
const browser = browserType.name();
20+
async function downloadBrowserWithProgressBar(downloadPath, browser, version = '') {
1921
let progressBar = null;
2022
let lastDownloadedBytes = 0;
21-
function onProgress(downloadedBytes, totalBytes) {
23+
const revision = packageJSON.playwright[`${browser}_revision`];
24+
function progress(downloadedBytes, totalBytes) {
2225
if (!progressBar) {
2326
const ProgressBar = require('progress');
24-
progressBar = new ProgressBar(`Downloading ${browser} ${browserType._revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
27+
progressBar = new ProgressBar(`Downloading ${browser} r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
2528
complete: '=',
2629
incomplete: ' ',
2730
width: 20,
2831
total: totalBytes,
32+
host: getFromENV('PLAYWRIGHT_DOWNLOAD_HOST'),
2933
});
3034
}
3135
const delta = downloadedBytes - lastDownloadedBytes;
3236
lastDownloadedBytes = downloadedBytes;
3337
progressBar.tick(delta);
3438
}
35-
36-
const fetcher = browserType._createBrowserFetcher();
37-
const revisionInfo = fetcher.revisionInfo();
38-
// Do nothing if the revision is already downloaded.
39-
if (revisionInfo.local)
40-
return revisionInfo;
41-
await browserType.downloadBrowserIfNeeded(onProgress);
42-
logPolitely(`${browser} downloaded to ${revisionInfo.folderPath}`);
43-
return revisionInfo;
39+
const executablePath = await browserFetcher.downloadBrowser({
40+
downloadPath,
41+
browser,
42+
revision,
43+
progress,
44+
});
45+
logPolitely(`${browser} downloaded to ${downloadPath}`);
46+
return executablePath;
4447
}
4548

46-
4749
function toMegabytes(bytes) {
4850
const mb = bytes / 1024 / 1024;
4951
return `${Math.round(mb * 10) / 10} Mb`;
@@ -57,4 +59,11 @@ function logPolitely(toBeLogged) {
5759
console.log(toBeLogged);
5860
}
5961

60-
module.exports = {downloadBrowser};
62+
function getFromENV(name) {
63+
let value = process.env[name];
64+
value = value || process.env[`npm_config_${name.toLowerCase()}`];
65+
value = value || process.env[`npm_package_config_${name.toLowerCase()}`];
66+
return value;
67+
}
68+
69+
module.exports = {downloadBrowserWithProgressBar};

index.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@
1515
*/
1616
const {Playwright} = require('./lib/server/playwright.js');
1717

18-
module.exports = new Playwright({
19-
downloadPath: __dirname,
18+
const playwright = new Playwright({
2019
browsers: ['webkit', 'chromium', 'firefox'],
21-
respectEnvironmentVariables: false,
2220
});
2321

22+
try {
23+
const downloadedBrowsers = require('./.downloaded-browsers.json');
24+
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
25+
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
26+
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
27+
} catch (e) {
28+
}
29+
30+
module.exports = playwright;
31+

install-from-github.js

+49-21
Original file line numberDiff line numberDiff line change
@@ -24,41 +24,69 @@ try {
2424
});
2525
} catch (e) {
2626
}
27-
const {downloadBrowser} = require('./download-browser');
28-
const playwright = require('.');
27+
28+
const path = require('path');
29+
const fs = require('fs');
30+
const util = require('util');
31+
const rmAsync = util.promisify(require('rimraf'));
32+
const existsAsync = path => fs.promises.access(path).then(() => true, e => false);
33+
const {downloadBrowserWithProgressBar} = require('./download-browser');
34+
const protocolGenerator = require('./utils/protocol-types-generator');
35+
const packageJSON = require('./package.json');
36+
37+
const DOWNLOADED_BROWSERS_JSON_PATH = path.join(__dirname, '.downloaded-browsers.json');
38+
const DOWNLOAD_PATHS = {
39+
chromium: path.join(__dirname, '.local-browsers', `chromium-${packageJSON.playwright.chromium_revision}`),
40+
firefox: path.join(__dirname, '.local-browsers', `firefox-${packageJSON.playwright.firefox_revision}`),
41+
webkit: path.join(__dirname, '.local-browsers', `webkit-${packageJSON.playwright.webkit_revision}`),
42+
};
2943

3044
(async function() {
31-
const protocolGenerator = require('./utils/protocol-types-generator');
45+
const downloadedBrowsersJSON = await fs.promises.readFile(DOWNLOADED_BROWSERS_JSON_PATH, 'utf8').then(json => JSON.parse(json)).catch(() => ({}));
3246
try {
33-
const chromeRevision = await downloadAndCleanup(playwright.chromium);
34-
await protocolGenerator.generateChromiunProtocol(chromeRevision);
47+
if (!(await existsAsync(DOWNLOAD_PATHS.chromium))) {
48+
const crExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.chromium, 'chromium');
49+
downloadedBrowsersJSON.crExecutablePath = crExecutablePath;
50+
await protocolGenerator.generateChromiumProtocol(crExecutablePath);
51+
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
52+
}
3553
} catch (e) {
3654
console.warn(e.message);
3755
}
38-
3956
try {
40-
const firefoxRevision = await downloadAndCleanup(playwright.firefox);
41-
await protocolGenerator.generateFirefoxProtocol(firefoxRevision);
57+
if (!(await existsAsync(DOWNLOAD_PATHS.firefox))) {
58+
const ffExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.firefox, 'firefox');
59+
downloadedBrowsersJSON.ffExecutablePath = ffExecutablePath;
60+
await protocolGenerator.generateFirefoxProtocol(ffExecutablePath);
61+
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
62+
}
4263
} catch (e) {
4364
console.warn(e.message);
4465
}
45-
4666
try {
47-
const webkitRevision = await downloadAndCleanup(playwright.webkit);
48-
await protocolGenerator.generateWebKitProtocol(webkitRevision);
67+
if (!(await existsAsync(DOWNLOAD_PATHS.webkit))) {
68+
const wkExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.webkit, 'webkit');
69+
downloadedBrowsersJSON.wkExecutablePath = wkExecutablePath;
70+
await protocolGenerator.generateWebKitProtocol(path.dirname(wkExecutablePath));
71+
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
72+
}
4973
} catch (e) {
5074
console.warn(e.message);
5175
}
52-
})();
5376

54-
async function downloadAndCleanup(browserType) {
55-
const revisionInfo = await downloadBrowser(browserType);
77+
// Cleanup stale revisions.
78+
const directories = new Set(await readdirAsync(path.join(__dirname, '.local-browsers')));
79+
directories.delete(DOWNLOAD_PATHS.chromium);
80+
directories.delete(DOWNLOAD_PATHS.firefox);
81+
directories.delete(DOWNLOAD_PATHS.webkit);
82+
// cleanup old browser directories.
83+
directories.add(path.join(__dirname, '.local-chromium'));
84+
directories.add(path.join(__dirname, '.local-firefox'));
85+
directories.add(path.join(__dirname, '.local-webkit'));
86+
await Promise.all([...directories].map(directory => rmAsync(directory)));
5687

57-
// Remove previous revisions.
58-
const fetcher = browserType._createBrowserFetcher();
59-
const localRevisions = await fetcher.localRevisions();
60-
const cleanupOldVersions = localRevisions.filter(revision => revision !== revisionInfo.revision).map(revision => fetcher.remove(revision));
61-
await Promise.all([...cleanupOldVersions]);
88+
async function readdirAsync(dirpath) {
89+
return fs.promises.readdir(dirpath).then(dirs => dirs.map(dir => path.join(dirpath, dir)));
90+
}
91+
})();
6292

63-
return revisionInfo;
64-
}

packages/playwright-chromium/index.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
const path = require('path');
1717
const {Playwright} = require('playwright-core/lib/server/playwright.js');
1818

19-
module.exports = new Playwright({
20-
downloadPath: __dirname,
19+
const playwright = new Playwright({
2120
browsers: ['chromium'],
22-
respectEnvironmentVariables: true,
2321
});
22+
module.exports = playwright;
2423

24+
try {
25+
const downloadedBrowsers = require('./.downloaded-browsers.json');
26+
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
27+
} catch (e) {
28+
throw new Error('playwright-chromium has not downloaded Chromium.');
29+
}

packages/playwright-chromium/install.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
const {downloadBrowser} = require('playwright-core/download-browser');
17-
const playwright = require('.');
18-
downloadBrowser(playwright.chromium);
16+
const path = require('path');
17+
const fs = require('fs');
18+
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
19+
(async function() {
20+
const crExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'chromium'), 'chromium');
21+
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({crExecutablePath}));
22+
})();

packages/playwright-firefox/index.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
const path = require('path');
1717
const {Playwright} = require('playwright-core/lib/server/playwright.js');
1818

19-
module.exports = new Playwright({
20-
downloadPath: __dirname,
19+
const playwright = new Playwright({
2120
browsers: ['firefox'],
22-
respectEnvironmentVariables: true,
2321
});
22+
module.exports = playwright;
23+
24+
try {
25+
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
26+
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
27+
} catch (e) {
28+
throw new Error('playwright-firefox has not downloaded Firefox.');
29+
}
2430

packages/playwright-firefox/install.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
const {downloadBrowser} = require('playwright-core/download-browser');
17-
const playwright = require('.');
18-
downloadBrowser(playwright.firefox);
16+
const path = require('path');
17+
const fs = require('fs');
18+
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
19+
20+
(async function() {
21+
const ffExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'firefox'), 'firefox');
22+
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({ffExecutablePath, }));
23+
})();

packages/playwright-webkit/index.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
const path = require('path');
1717
const {Playwright} = require('playwright-core/lib/server/playwright.js');
1818

19-
module.exports = new Playwright({
20-
downloadPath: __dirname,
19+
const playwright = new Playwright({
2120
browsers: ['webkit'],
22-
respectEnvironmentVariables: true,
2321
});
22+
module.exports = playwright;
23+
24+
try {
25+
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
26+
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
27+
} catch (e) {
28+
throw new Error('playwright-webkit has not downloaded WebKit.');
29+
}
2430

packages/playwright-webkit/install.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
const {downloadBrowser} = require('playwright-core/download-browser');
17-
const playwright = require('.');
18-
downloadBrowser(playwright.webkit);
16+
const path = require('path');
17+
const fs = require('fs');
18+
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
19+
20+
(async function() {
21+
const wkExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'webkit'), 'webkit');
22+
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({wkExecutablePath, }));
23+
})();

packages/playwright/index.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,21 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
const path = require('path');
1617
const {Playwright} = require('playwright-core/lib/server/playwright.js');
1718

18-
module.exports = new Playwright({
19-
downloadPath: __dirname,
19+
const playwright = new Playwright({
2020
browsers: ['webkit', 'chromium', 'firefox'],
21-
respectEnvironmentVariables: true,
2221
});
22+
module.exports = playwright;
23+
24+
try {
25+
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
26+
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
27+
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
28+
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
29+
} catch (e) {
30+
throw new Error('ERROR: Playwright did not download browsers');
31+
}
32+
2333

packages/playwright/install.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
const {downloadBrowser} = require('playwright-core/download-browser');
17-
const playwright = require('.');
16+
const path = require('path');
17+
const fs = require('fs');
18+
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
19+
1820
(async function() {
19-
await downloadBrowser(playwright.chromium);
20-
await downloadBrowser(playwright.firefox);
21-
await downloadBrowser(playwright.webkit);
21+
const crExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'chromium'), 'chromium');
22+
const ffExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'firefox'), 'firefox');
23+
const wkExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'webkit'), 'webkit');
24+
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({crExecutablePath, ffExecutablePath, wkExecutablePath, }));
2225
})();

0 commit comments

Comments
 (0)