Skip to content
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@ jobs:
- name: Run tests
run: deno test -A --unstable tests.ts

- name: Uninstall Chromium
run: deno run -A --unstable uninstall.ts

- name: Run tests without Chromium preinstalled
run: deno test -A --unstable tests.ts

- name: Run tests with lib.dom typings
run: deno test -A --unstable --config fixtures/dom-tsconfig.json tests.ts

docker:
name: docker
runs-on: ubuntu-latest

steps:
- name: Setup repo
uses: actions/checkout@v2
Expand All @@ -54,4 +60,4 @@ jobs:
run: docker build -t denopuppeteer .

- name: Run container
run: docker run -i denopuppeteer
run: docker run -i denopuppeteer
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16
PUPPETEER_PRODUCT=firefox deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
```

Alternately, if `puppeteer.launch` is called without specifying `executablePath`, then Puppeteer will be cached as well.

If, for some reason, you need to uninstall the Puppeteer cache, run these commands:

```shell
PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@9.0.1/uninstall.ts
PUPPETEER_PRODUCT=firefox deno run -A --unstable https://deno.land/x/puppeteer@9.0.1/uninstall.ts
```

You can find all of the supported environment variables to customize
installation
[in the Puppeteer docs](https://pptr.dev/#?product=Puppeteer&version=v16.2.0&show=api-environment-variables).
Expand Down
137 changes: 94 additions & 43 deletions install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,102 @@ import puppeteer from "./mod.ts";
import { PUPPETEER_REVISIONS } from "./vendor/puppeteer-core/puppeteer/revisions.js";
import ProgressBar from "https://deno.land/x/progress@v1.1.4/mod.ts";

let product = Deno.env.get("PUPPETEER_PRODUCT");
if (product != "chrome" && product != "firefox") {
if (product != undefined) {
console.warn(`Unknown product '${product}', falling back to 'chrome'.`);
}
product = "chrome";
/** Logging verbosity. */
export type LogLevel = "default" | "minimal";

/**
* Options to use when downloading.
*/
export interface InstallOptions {
/** Print log messages. */
enableLog?: boolean;
/** Which logs to print. */
logLevel?: LogLevel;
/** Browser to install. */
product?: "chrome" | "firefox";
/** Chrome or Firefox version to install. */
revision?: string;
}
const fetcher = puppeteer.createBrowserFetcher({ product });
let revision;
if (product == "chrome") {
revision = Deno.env.get("PUPPETEER_CHROMIUM_REVISION") ||
PUPPETEER_REVISIONS.chromium;
} else if (product == "firefox") {
puppeteer._preferredRevision = PUPPETEER_REVISIONS.firefox;
const req = await fetch(
"https://product-details.mozilla.org/1.0/firefox_versions.json",
);
const versions = await req.json();
revision = versions.FIREFOX_NIGHTLY;
if (!versions.FIREFOX_NIGHTLY) {
throw new Error("Firefox version not found");

const DEFAULT_OPTIONS: InstallOptions = {
enableLog: true,
logLevel: "default",
product: "chrome",
};

/**
* Install and cache a suitable browser to use for puppeteer.
*
* @param options Install options.
*/
export async function installPuppeteer(
options: InstallOptions = DEFAULT_OPTIONS,
) {
const logLevel = options.logLevel || "default";
let product = Deno.env.get("PUPPETEER_PRODUCT") || options.product;
if (product != "chrome" && product != "firefox") {
if (product != undefined && options.enableLog && logLevel === "default") {
console.warn(`Unknown product '${product}', falling back to 'chrome'.`);
}
product = "chrome";
}
const fetcher = puppeteer.createBrowserFetcher({ product });
let revision;
if (product == "chrome") {
revision = Deno.env.get("PUPPETEER_CHROMIUM_REVISION") ||
options.revision ||
PUPPETEER_REVISIONS.chromium;
} else if (product == "firefox") {
puppeteer._preferredRevision = options.revision ||
PUPPETEER_REVISIONS.firefox;
const req = await fetch(
"https://product-details.mozilla.org/1.0/firefox_versions.json",
);
const versions = await req.json();
revision = versions.FIREFOX_NIGHTLY;
if (!versions.FIREFOX_NIGHTLY) {
throw new Error("Firefox version not found");
}
}

const revisionInfo = fetcher.revisionInfo(revision);
if (revisionInfo.local) {
if (options.enableLog && logLevel === "default") {
console.log(`Already downloaded at ${revisionInfo.executablePath}`);
}
} else {
if (options.enableLog && logLevel === "minimal") {
console.log(`Downloading ${product}`);
}

let progressBar: ProgressBar;
const newRevisionInfo = await fetcher.download(
revisionInfo.revision,
(current, total) => {
if (!options.enableLog || (options.enableLog && logLevel === "minimal")) {
return;
}
if (!progressBar) {
progressBar = new ProgressBar({
total,
});
}
if (!(progressBar as any).isCompleted) {
progressBar.render(current);
} else if (options.enableLog && logLevel === "default") {
console.log("Done downloading. Installing now.");
}
},
);
if (options.enableLog && logLevel === "default") {
console.log(
`Downloaded ${newRevisionInfo.product} ${newRevisionInfo.revision} to ${newRevisionInfo.executablePath} from ${newRevisionInfo.url}`,
);
}
}
}

const revisionInfo = fetcher.revisionInfo(revision);
if (revisionInfo.local) {
console.log(`Already downloaded at ${revisionInfo.executablePath}`);
} else {
let progressBar: ProgressBar;
const newRevisionInfo = await fetcher.download(
revisionInfo.revision,
(current, total) => {
if (!progressBar) {
progressBar = new ProgressBar({
total,
});
}
if (!(progressBar as any).isCompleted) {
progressBar.render(current);
} else {
console.log("Done downloading. Installing now.");
}
},
);
console.log(
`Downloaded ${newRevisionInfo.product} ${newRevisionInfo.revision} to ${newRevisionInfo.executablePath} from ${newRevisionInfo.url}`,
);
if (import.meta.main) {
await installPuppeteer();
Deno.exit(0);
}
15 changes: 13 additions & 2 deletions src/deno/Puppeteer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Browser } from "../../vendor/puppeteer-core/puppeteer/common/Browser.js
import Launcher, { ProductLauncher } from "./Launcher.ts";
import { PUPPETEER_REVISIONS } from "../../vendor/puppeteer-core/puppeteer/revisions.js";
import { Product } from "../../vendor/puppeteer-core/puppeteer/common/Product.js";
import { installPuppeteer, LogLevel } from "../../install.ts";

/**
* Extends the main {@link Puppeteer} class with Node specific behaviour for fetching and
Expand Down Expand Up @@ -64,6 +65,8 @@ import { Product } from "../../vendor/puppeteer-core/puppeteer/common/Product.js
export class PuppeteerDeno extends Puppeteer {
private _lazyLauncher!: ProductLauncher;
private __productName?: Product;
private _downloaded: boolean = false;

/**
* @internal
*/
Expand Down Expand Up @@ -121,23 +124,31 @@ export class PuppeteerDeno extends Puppeteer {
* **NOTE** Puppeteer can also be used to control the Chrome browser,
* but it works best with the version of Chromium it is bundled with.
* There is no guarantee it will work with any other version.
* Use `executablePath` option with extreme caution.
* Use `executablePath` option with extreme caution. If it is not specified then the puppeteer will be downloaded and installed if necessary.
* If Google Chrome (rather than Chromium) is preferred, a {@link https://www.google.com/chrome/browser/canary.html | Chrome Canary} or {@link https://www.chromium.org/getting-involved/dev-channel | Dev Channel} build is suggested.
* In `puppeteer.launch([options])`, any mention of Chromium also applies to Chrome.
* See {@link https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/ | this article} for a description of the differences between Chromium and Chrome. {@link https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md | This article} describes some differences for Linux users.
*
* @param options - Set of configurable options to set on the browser.
* @returns Promise which resolves to browser instance.
*/
launch(
async launch(
options: LaunchOptions &
ChromeArgOptions &
BrowserConnectOptions & {
product?: Product;
extraPrefsFirefox?: Record<string, unknown>;
enableLog?: boolean;
logLevel?: LogLevel;
} = {}
): Promise<Browser> {
if (options.product) this._productName = options.product;
if (!options.executablePath && !this._downloaded) {
const enableLog = options.enableLog !== undefined ? options.enableLog : true;
const logLevel = options.logLevel !== undefined ? options.logLevel : "default";
await installPuppeteer({ enableLog, logLevel });
this._downloaded = true;
}
return this._launcher.launch(options);
}

Expand Down
18 changes: 11 additions & 7 deletions tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ function browserTest(
name: string,
fn: (browser: Browser) => void | Promise<void>,
) {
Deno.test(name, async () => {
let browser: Browser | undefined = undefined;
try {
browser = await puppeteer.launch({});
await fn(browser);
} finally {
if (browser) await browser.close();
Deno.test({
name,
sanitizeResources: false,
fn: async () => {
let browser: Browser | undefined = undefined;
try {
browser = await puppeteer.launch({});
await fn(browser);
} finally {
if (browser) await browser.close();
}
}
});
}
Expand Down
49 changes: 49 additions & 0 deletions uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import puppeteer from "./mod.ts";
import { PUPPETEER_REVISIONS } from "./vendor/puppeteer-core/puppeteer/revisions.js";
import { InstallOptions } from "./install.ts";

const DEFAULT_OPTIONS: InstallOptions = {
enableLog: true,
product: "chrome",
};

/**
* Remove a cached revision.
*
* @param options Uninstall options.
*/
export async function uninstallPuppeteer(
options: InstallOptions = DEFAULT_OPTIONS,
) {
let product = Deno.env.get("PUPPETEER_PRODUCT") || options.product;
if (product != "chrome" && product != "firefox") {
if (product != undefined && options.enableLog) {
console.warn(`Unknown product '${product}', falling back to 'chrome'.`);
}
product = "chrome";
}
const fetcher = puppeteer.createBrowserFetcher({ product });
let revision;
if (product == "chrome") {
revision = Deno.env.get("PUPPETEER_CHROMIUM_REVISION") ||
options.revision ||
PUPPETEER_REVISIONS.chromium;
} else if (product == "firefox") {
puppeteer._preferredRevision = options.revision ||
PUPPETEER_REVISIONS.firefox;
const req = await fetch(
"https://product-details.mozilla.org/1.0/firefox_versions.json",
);
const versions = await req.json();
revision = versions.FIREFOX_NIGHTLY;
if (!versions.FIREFOX_NIGHTLY) {
throw new Error("Firefox version not found");
}
}

await fetcher.remove(revision);
}

if (import.meta.main) {
await uninstallPuppeteer();
}