Skip to content

Commit

Permalink
feat(browser): Add new browsercommand and sub-commands
Browse files Browse the repository at this point in the history
Implements #394
  • Loading branch information
Göran Sander committed Jul 16, 2023
1 parent b688758 commit c5db184
Show file tree
Hide file tree
Showing 9 changed files with 612 additions and 16 deletions.
125 changes: 125 additions & 0 deletions src/__tests__/browser.test.js
@@ -0,0 +1,125 @@
/* eslint-disable no-console */
const { test, expect, describe } = require('@jest/globals');

const { browserInstalled } = require('../lib/browser/browser-installed');
const { browserInstall } = require('../lib/browser/browser-install');
const { browserUninstallAll } = require('../lib/browser/browser-uninstall');

const defaultTestTimeout = process.env.BSI_TEST_TIMEOUT || 600000; // 10 minute default timeout

console.log(`Jest timeout: ${defaultTestTimeout}`);

const options = {
loglevel: process.env.BSI_LOG_LEVEL || 'info',
};

jest.setTimeout(defaultTestTimeout);

describe('test browser list_installed command', () => {
/**
* List installed browsers
* Should return an array of installed browser, zero or more entries
*/
test('list installed browsers, should be zero or more', async () => {
const installedBrowsers = await browserInstalled(options);
expect(installedBrowsers.length).toBeGreaterThanOrEqual(0);
});
});

describe('complex/combiation scenarios', () => {
/**
* Remove all installed browsers
* Should return true.
*
* Install four differenct browsers (3 chrome versions, 1 firefox).
* There should now be four installed browsers
*
* Remove all installed browsers.
* Should return true.
*
* There should then be zero installed browsers.
*/
test('install and uninstall several browsers', async () => {
// Remove all installed browsers
const uninstallRes1 = await browserUninstallAll(options);
expect(uninstallRes1).toEqual(true);

// There should now be zero installed browsers
const installedBrowsers1 = await browserInstalled(options);
expect(installedBrowsers1.length).toEqual(0);

// Install four different browsers
await browserInstall({ browser: 'chrome', browserVersion: 'stable' }); // Same as previous, should not install another browser
await browserInstall({ browser: 'chrome', browserVersion: 'dev' });
await browserInstall({ browser: 'chrome', browserVersion: 'canary' });
await browserInstall({ browser: 'firefox', browserVersion: 'latest' }); // Same as previous, should not install another browser

// There should now be four installed browsers
const installedBrowsers2 = await browserInstalled(options);
expect(installedBrowsers2.length).toEqual(4);

// Remove all installed browsers
const uninstallRes2 = await browserUninstallAll(options);
expect(uninstallRes2).toEqual(true);

// There should now be zero installed browsers
const installedBrowsers3 = await browserInstalled(options);
expect(installedBrowsers3.length).toEqual(0);
});
});

describe('install/uninstall browser scenarios', () => {
/**
* Install a browser that exists
* Should return true.
* There should then be one installed browser.
*/
test('install a browser that exists', async () => {
// Remove all installed browsers
const uninstallRes1 = await browserUninstallAll(options);
expect(uninstallRes1).toEqual(true);

// There should now be zero installed browsers
const installedBrowsers1 = await browserInstalled(options);
expect(installedBrowsers1.length).toEqual(0);

// Install a browser
await browserInstall({ browser: 'chrome', browserVersion: 'stable' });

// There should now be one installed browser
const installedBrowsers2 = await browserInstalled(options);
expect(installedBrowsers2.length).toEqual(1);

// Remove all installed browsers
const uninstallRes2 = await browserUninstallAll(options);
expect(uninstallRes2).toEqual(true);

// There should now be zero installed browsers
const installedBrowsers3 = await browserInstalled(options);
expect(installedBrowsers3.length).toEqual(0);
});

/**
* Install a browser that does not exist
* Should throw an error.
* There should then be zero installed browsers.
*/
test('install a browser that does not exist', async () => {
// Remove all installed browsers
const uninstallRes1 = await browserUninstallAll(options);
expect(uninstallRes1).toEqual(true);

// There should now be zero installed browsers
const installedBrowsers1 = await browserInstalled(options);
expect(installedBrowsers1.length).toEqual(0);

// Install a browser
await expect(
browserInstall({ browser: 'chrome', browserVersion: 'non-existent' })
).rejects.toThrow();

// There should now be zero installed browsers
const installedBrowsers2 = await browserInstalled(options);
expect(installedBrowsers2.length).toEqual(0);
});
});
135 changes: 133 additions & 2 deletions src/butler-sheet-icons.js
Expand Up @@ -7,6 +7,9 @@ const { qscloudCreateThumbnails } = require('./lib/cloud/cloud-create-thumbnails
const { qscloudListCollections } = require('./lib/cloud/cloud-collections');
const { qscloudRemoveSheetIcons } = require('./lib/cloud/cloud-remove-sheet-icons');
const { browserInstalled } = require('./lib/browser/browser-installed');
const { browserInstall } = require('./lib/browser/browser-install');
const { browserUninstall, browserUninstallAll } = require('./lib/browser/browser-uninstall');
const { browserListAvailable } = require('./lib/browser/browser-list-available');

const program = new Command();

Expand Down Expand Up @@ -344,8 +347,9 @@ const program = new Command();
function makeBrowserCommand() {
const browser = new Command('browser');

// "installed" sub-command
browser
.command('installed')
.command('list-installed')
.description(
'Show which browsers are currently installed and available for use by Butler Sheet Icons.'
)
Expand All @@ -355,7 +359,7 @@ const program = new Command();
const res = await browserInstalled(options, command);
logger.debug(`Call to browserInstalled succeeded: ${res}`);
} catch (err) {
logger.error(`MAIN browser: ${err}`);
logger.error(`MAIN browser installed: ${err}`);
}
})
.addOption(
Expand All @@ -364,6 +368,133 @@ const program = new Command();
.default('info')
);

// uninstall sub-command
browser
.command('uninstall')
.description(
'Uninstall a browser from the Butler Sheet Icons cache.\nThis will remove the browser from the cache, but will not affect other browsers on this computer.\nUse the "butler-sheet-icons browser installed" command to see which browsers are currently installed.'
)
.action(async (options, command) => {
try {
const res = await browserUninstall(options, command);
logger.debug(`Call to browserUninstall succeeded: ${res}`);
} catch (err) {
logger.error(`MAIN browser uninstall: ${err}`);
}
})
.addOption(
new Option('--loglevel <level>', 'log level')
.choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
.default('info')
)
.requiredOption(
'--browser <browser>',
'Browser to uninstall (e.g. "chrome" or "firefox"). Use "butler-sheet-icons browser installed" to see which browsers are currently installed.',
'chrome'
)
.requiredOption(
'--browser-version <version>',
'Version (=build id) of the browser to uninstall. Use "butler-sheet-icons browser installed" to see which browsers are currently installed.'
);

// uninstall-all sub-command
browser
.command('uninstall-all')
.description(
'Uninstall all browsers from the Butler Sheet Icons cache.\nThis will remove all browsers from the cache, but will not affect other browsers on this computer.\nUse the "butler-sheet-icons browser installed" command to see which browsers are currently installed.'
)
.action(async (options, command) => {
try {
const res = await browserUninstallAll(options, command);
logger.debug(`Call to browserUninstallAll succeeded: ${res}`);
} catch (err) {
logger.error(`MAIN browser uninstall-all: ${err}`);
}
})
.addOption(
new Option('--loglevel <level>', 'log level')
.choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
.default('info')
);

// install sub-command
browser
.command('install')
.description(
'Install a browser into the Butler Sheet Icons cache.\nThis will download the browser and install it into the cache, where it can be used by Butler Sheet Icons.\nUse the "butler-sheet-icons browser installed" command to see which browsers are currently installed.'
)
.action(async (options, command) => {
try {
// Set browser version per browser
if (!options.browserVersion || options.browserVersion === '') {
if (options.browser === 'chrome') {
// eslint-disable-next-line no-param-reassign
options.browserVersion = 'stable';
} else if (options.browser === 'firefox') {
// eslint-disable-next-line no-param-reassign
options.browserVersion = 'latest';
}
}
const res = await browserInstall(options, command);
logger.debug(`Call to browserInstall succeeded: ${res}`);
} catch (err) {
logger.error(`MAIN browser install: ${err}`);
}
})
.addOption(
new Option('--loglevel <level>', 'log level')
.choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
.default('info')
)
.addOption(
new Option(
'--browser <browser>',
'Browser to install (e.g. "chrome" or "firefox"). Use "butler-sheet-icons browser installed" to see which browsers are currently installed.'
)
.choices(['chrome', 'firefox'])
.default('chrome')
)
.option(
'--browser-version <version>',
'Version (=build id) of the browser to install. Use "butler-sheet-icons browser installed" to see which browsers are currently installed.'
);

// available sub-command
browser
.command('list-available')
.description(
'Show which browsers are available for download and installation by Butler Sheet Icons.'
)
.action(async (options, command) => {
try {
const res = await browserListAvailable(options, command);
logger.debug(`Call to browserAvailable succeeded: ${res}`);
} catch (err) {
logger.error(`MAIN browser available: ${err}`);
}
})

.addOption(
new Option('--loglevel <level>', 'log level')
.choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
.default('info')
)
.addOption(
new Option(
'--browser <browser>',
'Browser to install (e.g. "chrome" or "firefox"). Use "butler-sheet-icons browser installed" to see which browsers are currently installed.'
)
.choices(['chrome', 'firefox'])
.default('chrome')
)
.addOption(
new Option(
'--channel <browser>',
"Which of the browser's release channel versions should be listed?\n This option is only used for Chrome."
)
.choices(['stable', 'beta', 'dev', 'canary'])
.default('stable')
);
return browser;
}

Expand Down
68 changes: 68 additions & 0 deletions src/lib/browser/browser-install.js
@@ -0,0 +1,68 @@
const { install, resolveBuildId, detectBrowserPlatform } = require('@puppeteer/browsers');
const path = require('path');
const { homedir } = require('os');

const { logger, setLoggingLevel, bsiExecutablePath, isPkg } = require('../../globals');

/**
* Install browser
* Returns true if browser installed successfully
* @param {object} options
* @param {string} options.browser - Browser to install
* @param {string} options.browserVersion - Browser version to install
* @returns {boolean} - True if browser installed successfully
* @throws {Error} - If browser not installed successfully
* @throws {Error} - If browser version not found
* @throws {Error} - If error installing browser
* @throws {Error} - If error resolving browser build id
* @throws {Error} - If error detecting browser platform
* @throws {Error} - If error getting browser cache path
* @throws {Error} - If error getting browser executable path
*/
// eslint-disable-next-line no-unused-vars
const browserInstall = async (options, _command) => {
try {
// Set log level
setLoggingLevel(options.loglevel);

logger.verbose('Starting browser install');
logger.verbose(`Running as standalone app: ${isPkg}`);
logger.debug(`BSI executable path: ${bsiExecutablePath}`);
logger.debug(`Options: ${JSON.stringify(options, null, 2)}`);

// Install browser
const browserPath = path.join(homedir(), '.cache/puppeteer');
logger.debug(`Browser cache path: ${browserPath}`);

const platform = await detectBrowserPlatform();
logger.debug(`Detected browser platform: ${platform}`);

// Determine which browser version to install
const buildId = await resolveBuildId(options.browser, platform, options.browserVersion);
logger.info(
`Resolved browser build id: "${buildId}" for browser "${options.browser}" version "${options.browserVersion}"`
);
logger.info('Installing browser...');

const browser = await install({
browser: options.browser,
buildId,
cacheDir: browserPath,
});

logger.info(`Browser "${browser.browser}" version "${browser.buildId}" installed`);

return true;
} catch (err) {
// Check if error is due to browser version missing
if (err.message.includes('Download failed: server returned code 404.')) {
logger.error(`Browser version "${options.browserVersion}" not found`);
} else {
logger.error(`Error installing browser: ${err}`);
}

throw err;
}
};

module.exports = { browserInstall };
8 changes: 5 additions & 3 deletions src/lib/browser/browser-installed.js
Expand Up @@ -4,7 +4,9 @@ const { homedir } = require('os');

const { logger, setLoggingLevel, bsiExecutablePath, isPkg } = require('../../globals');

const browserInstalled = async (options) => {
// Function to list all installed browsers
// Returns an array of installed browsers
async function browserInstalled(options) {
try {
// Set log level
setLoggingLevel(options.loglevel);
Expand Down Expand Up @@ -33,11 +35,11 @@ const browserInstalled = async (options) => {
logger.info('No browsers installed');
}

return true;
return browsersInstalled;
} catch (err) {
logger.error(`Error checking for installed browsers: ${err}`);
throw err;
}
};
}

module.exports = { browserInstalled };

0 comments on commit c5db184

Please sign in to comment.