Skip to content

Commit

Permalink
Add browser launchers for chrome, chromium, and edge
Browse files Browse the repository at this point in the history
  • Loading branch information
Krinkle committed Jan 5, 2025
1 parent c884270 commit 2dc891a
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 18 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
matrix:
include:
- name: "Linux: Node 20"
# Includes firefox, chromium
# Includes firefox, chromium, microsoft-edge
# https://github.com/actions/runner-images/blob/ubuntu24/20241215.1/images/ubuntu/Ubuntu2404-Readme.md
os: ubuntu-24.04
node: 20.x
Expand All @@ -26,7 +26,7 @@ jobs:
os: windows-latest
node: 20.x

# Includes Firefox, Google Chrome, Safari
# Includes Firefox, Google Chrome, Microsoft Edge, Safari
# https://github.com/actions/runner-images/blob/macos-13/20241216.479/images/macos/macos-13-Readme.md
- name: "macOS: Node 20"
os: macos-13
Expand Down Expand Up @@ -55,3 +55,5 @@ jobs:

- run: npm test

- name: Check system browsers
run: node bin/qtap.js -v -b firefox -b chrome -b chromium -b edge test/pass.html
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default [
{
rules: {
'comma-dangle': 'off',
'multiline-ternary': 'off',
'operator-linebreak': ['error', 'before'],
}
}
Expand Down
116 changes: 102 additions & 14 deletions src/browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import os from 'node:os';
import path from 'node:path';

import which from 'which';
import { concatGenFn } from './util.js';

const QTAP_DEBUG = process.env.QTAP_DEBUG === '1';
const tempDirs = [];
Expand Down Expand Up @@ -85,7 +86,7 @@ const LocalBrowser = {
if (!signal.aborted) {
reject(new Error(details));
} else {
logger.debug('browser_natural_exit', details);
logger.debug('browser_natural_exit', `Process exitted with code ${code} and signal ${sig}`);
resolve();
}
});
Expand Down Expand Up @@ -126,6 +127,21 @@ const LocalBrowser = {
}
};

// - use Set to remove duplicate values, because `PROGRAMFILES` and `ProgramW6432` are often
// both "C:\Program Files", which, we'd check three times otherwise.
// - it is important that this preserves order of precedence.
// - use filter() to remove empty/unset environment variables.
//
// https://github.com/karma-runner/karma-chrome-launcher/blob/v3.2.0/index.js
// https://github.com/vweevers/win-detect-browsers/blob/v7.0.0/lib/browsers.js
const WINDOWS_DIRS = new Set([
process.env.LOCALAPPDATA,
process.env.PROGRAMFILES,
process.env['PROGRAMFILES(X86)'],
process.env.ProgramW6432,
'C:\\Program Files'
].filter(Boolean));

function createFirefoxPrefsJs (prefs) {
let js = '';
for (const key in prefs) {
Expand All @@ -135,23 +151,75 @@ function createFirefoxPrefsJs (prefs) {
}

function * getFirefoxPaths () {
yield process.env.FIREFOX_BIN;

// Handle unix-like platforms such as linux, WSL, darwin/macOS, freebsd, openbsd.
// Note that firefox-esr on Debian/Ubuntu includes a 'firefox' alias.
//
// Example: /usr/bin/firefox
yield process.env.FIREFOX_BIN;
yield which.sync('firefox', { nothrow: true });

if (process.platform === 'darwin') {
if (process.env.HOME) yield process.env.HOME + '/Applications/Firefox.app/Contents/MacOS/firefox';
yield '/Applications/Firefox.app/Contents/MacOS/firefox';
const appPath = '/Applications/Firefox.app/Contents/MacOS/firefox';
if (process.env.HOME) yield process.env.HOME + appPath;
yield appPath;
}

if (process.platform === 'win32') {
for (const dir of WINDOWS_DIRS) yield dir + '\\Mozilla Firefox\\firefox.exe';
}
}

function * getChromePaths () {
yield process.env.CHROME_BIN;
yield which.sync('google-chrome', { nothrow: true });
yield which.sync('google-chrome-stable', { nothrow: true });

if (process.platform === 'darwin') {
const appPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
if (process.env.HOME) yield process.env.HOME + appPath;
yield appPath;
}

if (process.platform === 'win32') {
for (const dir of WINDOWS_DIRS) yield dir + '\\Google\\Chrome\\Application\\chrome.exe';
}
}

function * getChromiumPaths () {
// Try 'chromium-browser' first to avoid conflict with 'chromium' from chromium-bsu on Debian
yield process.env.CHROMIUM_BIN;
yield which.sync('chromium-browser', { nothrow: true });
yield which.sync('chromium', { nothrow: true });

if (process.platform === 'darwin') {
const appPath = '/Applications/Chromium.app/Contents/MacOS/Chromium';
if (process.env.HOME) yield process.env.HOME + appPath;
yield appPath;
}

if (process.platform === 'win32') {
if (process.env.PROGRAMFILES) yield process.env.PROGRAMFILES + '\\Mozilla Firefox\\firefox.exe';
if (process.env['PROGRAMFILES(X86)']) yield process.env['PROGRAMFILES(X86)'] + '\\Mozilla Firefox\\firefox.exe';
yield 'C:\\Program Files\\Mozilla Firefox\\firefox.exe';
for (const dir of WINDOWS_DIRS) yield dir + '\\Chromium\\Application\\chrome.exe';
}
}

function * getEdgePaths () {
// Debian packages from https://packages.microsoft.com
// https://learn.microsoft.com/en-us/linux/packages
// https://github.com/actions/runner-images/blob/1ffc99a7ae/images/ubuntu/scripts/build/install-microsoft-edge.sh#L11
// https://github.com/microsoft/playwright/blob/v1.49.1/packages/playwright-core/src/server/registry/index.ts#L560
yield process.env.EDGE_BIN;
yield which.sync('microsoft-edge', { nothrow: true });
yield which.sync('microsoft-edge-stable', { nothrow: true });
yield '/opt/microsoft/msedge/msedge';

if (process.platform === 'darwin') {
const appPath = '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge';
if (process.env.HOME) yield process.env.HOME + appPath;
yield appPath;
}

if (process.platform === 'win32') {
for (const dir of WINDOWS_DIRS) yield dir + '\\Microsoft\\Edge\\Application\\msedge.exe';
}
}

Expand Down Expand Up @@ -192,22 +260,42 @@ async function firefox (url, signal, logger) {
'startup.homepage_welcome_url': '', // Blank start, disable extra tab
'startup.homepage_welcome_url.additional': '', // Blank start, disable extra tab
}));
await LocalBrowser.spawn(getFirefoxPaths(), args, signal, logger);
}

await LocalBrowser.spawn(getFirefoxCandidates(), args, signal, logger);
async function chromium (paths, url, signal, logger) {
const dataDir = LocalBrowser.makeTempDir();
// https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
const args = [
'--user-data-dir=' + dataDir,
'--no-default-browser-check',
'--no-first-run',
'--disable-default-apps',
'--disable-popup-blocking',
'--disable-translate',
'--disable-background-timer-throttling',
...(QTAP_DEBUG ? [] : [
'--headless',
'--disable-gpu',
'--disable-dev-shm-usage'
]),
...(process.env.CHROMIUM_FLAGS ? process.env.CHROMIUM_FLAGS.split(/\s+/) : []),
url
];
await LocalBrowser.spawn(paths, args, signal, logger);
}

export default {
LocalBrowser,

firefox,
// https://github.com/vweevers/win-detect-browsers/blob/v7.0.0/lib/browsers.js
chrome: chromium.bind(null, concatGenFn(getChromePaths, getChromiumPaths, getEdgePaths)),
chromium: chromium.bind(null, concatGenFn(getChromiumPaths, getChromePaths, getEdgePaths)),
edge: chromium.bind(null, concatGenFn(getEdgePaths)),

//
// TODO: safari: [],
// TODO: chromium: [], // chromium+chrome+edge
// --no-sandbox CHROMIUM_FLAGS

// TODO: chrome: [], // chrome+chromium+edge
// TODO: edge: [], // edge+chrome+chromium
// TODO: browserstack
// - browserstack/firefox_45
// - browserstack/firefox_previous
Expand Down
4 changes: 2 additions & 2 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class ControlServer {
async launchBrowser (browserFn, browserName) {
const clientId = 'client_' + this.constructor.nextClientId++;
const url = await this.getProxyBase() + '/?qtap_clientId=' + clientId;
const logger = this.logger.channel(`qtap_browser_${browserName}_${clientId}`);
const logger = this.logger.channel(`qtap_browser_${clientId}_${browserName}`);

const controller = new AbortController();
const summary = { ok: true };
Expand Down Expand Up @@ -200,7 +200,7 @@ class ControlServer {
}

try {
logger.debug('browser_launch_start');
logger.debug('browser_launch_call');
await browserFn(url, signal, logger);
logger.debug('browser_launch_ended');
} catch (err) {
Expand Down
6 changes: 6 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ export function humanSeconds (msDuration) {
.toFixed(1)
.replace(/\.(0+)?$/, '');
}

export function * concatGenFn (...fns) {
for (const fn of fns) {
yield * fn();
}
}

0 comments on commit 2dc891a

Please sign in to comment.