├── .github ├── CODEOWNERS └── workflows │ ├── publish-npm.yml │ ├── release.yml │ ├── semantic.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── chromedriver.js ├── download-chromedriver.js ├── package-lock.json ├── package.json ├── script ├── publish.js └── update-version.js └── test └── chromedriver-test.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This Repo is an AOR of the Releases Working Group 2 | * @electron/wg-releases -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish npm Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.[0-9]+.[0-9]+ 7 | 8 | jobs: 9 | test: 10 | uses: ./.github/workflows/test.yml 11 | with: 12 | electron-version: ${{ github.ref_name }} 13 | release: 14 | runs-on: ubuntu-latest 15 | needs: test 16 | environment: npm 17 | permissions: 18 | contents: write # for creating new release 19 | id-token: write # for CFA 20 | steps: 21 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 22 | - name: "Use Node.js ${{ matrix.node-version }}" 23 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 24 | with: 25 | node-version: "20.16.0" 26 | - name: Update Version 27 | run: node script/update-version.js ${{ github.ref_name }} 28 | - name: Confirm Version Updated 29 | run: node -e "if (require('./package.json').version === '0.0.0-development') process.exit(1)" 30 | - name: Install Dependencies 31 | run: npm ci 32 | - name: Obtain OIDC token 33 | id: oidc 34 | run: | 35 | token=$(curl --fail -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ 36 | "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=continuousauth.dev" | jq -r '.value') 37 | echo "::add-mask::${token}" 38 | echo "token=${token}" >> $GITHUB_OUTPUT 39 | - name: Obtain GitHub credentials 40 | id: github_creds 41 | run: | 42 | token=$(curl --fail "https://continuousauth.dev/api/request/${{ secrets.CFA_PROJECT_ID }}/github/credentials" \ 43 | -X POST \ 44 | -H "Content-Type: application/json" \ 45 | -H "Authorization: bearer ${{ secrets.CFA_SECRET }}" \ 46 | --data "{\"token\":\"${{ steps.oidc.outputs.token }}\"}" | jq -r '.GITHUB_TOKEN') 47 | echo "::add-mask::${token}" 48 | echo "token=${token}" >> $GITHUB_OUTPUT 49 | - name: Set NPM Credentials 50 | run: echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} > ~/.npmrc 51 | - name: Check NPM Credentials 52 | run: npm whoami 53 | - name: CFA Publish 54 | env: 55 | CFA_PROJECT_ID: ${{ secrets.CFA_PROJECT_ID }} 56 | CFA_SECRET: ${{ secrets.CFA_SECRET }} 57 | GITHUB_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} 58 | run: node script/publish.js 59 | - name: Create Release 60 | env: 61 | GITHUB_TOKEN: ${{ steps.github_creds.outputs.token }} 62 | run: gh release create ${{ github.ref_name }} -t ${{ github.ref_name }} 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release For New Electron Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Electron version to use with "v" prefix (e.g. v30.0.0) 8 | required: true 9 | 10 | jobs: 11 | test: 12 | uses: ./.github/workflows/test.yml 13 | with: 14 | electron-version: ${{ github.event.inputs.version }} 15 | tag_new_version: 16 | runs-on: ubuntu-latest 17 | environment: deps-releaser 18 | needs: test 19 | steps: 20 | - name: Generate GitHub App token 21 | uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1 22 | id: generate-token 23 | with: 24 | creds: ${{ secrets.DEPS_RELEASER_GH_APP_CREDS }} 25 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 26 | with: 27 | token: ${{ steps.generate-token.outputs.token }} 28 | # Tag here, the publish-npm.yml workflow will trigger on the new tag and do the CFA publish 29 | - name: Push New Tag 30 | run: | 31 | git tag ${{ github.event.inputs.version }} 32 | git push origin ${{ github.event.inputs.version }} 33 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | name: "Check Semantic Commit" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | permissions: 16 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 17 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR 18 | name: Validate PR Title 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: semantic-pull-request 22 | uses: amannn/action-semantic-pull-request@01d5fd8a8ebb9aafe902c40c53f0f4744f7381eb # tag: v5 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | validateSingleCommit: false 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | electron-version: 7 | required: true 8 | type: string 9 | workflow_dispatch: 10 | schedule: 11 | - cron: '0 19 * * 1-5' 12 | push: 13 | branches: 14 | - main 15 | pull_request: 16 | branches: 17 | - main 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | test: 24 | defaults: 25 | run: 26 | shell: bash 27 | strategy: 28 | matrix: 29 | node-version: 30 | - '20.16.0' 31 | - '18.20.4' 32 | - '16.20.2' 33 | - '14.21.3' 34 | os: 35 | - macos-latest 36 | - ubuntu-latest 37 | - windows-latest 38 | exclude: 39 | - os: macos-latest 40 | node-version: 14.21.3 41 | runs-on: "${{ matrix.os }}" 42 | steps: 43 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 44 | - name: "Use Node.js ${{ matrix.node-version }}" 45 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 46 | with: 47 | node-version: "${{ matrix.node-version }}" 48 | - name: Update Version 49 | if: ${{ inputs.electron-version != '' }} 50 | run: node script/update-version.js ${{ inputs.electron-version }} 51 | - name: Use Latest Electron Version 52 | if: ${{ inputs.electron-version == '' }} 53 | run: echo "ELECTRON_CHROMEDRIVER_STABLE_FALLBACK=1" >> $GITHUB_ENV 54 | - name: Install Dependencies 55 | run: npm ci 56 | - name: Run Tests 57 | run: | 58 | node --version 59 | npm --version 60 | npm test 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | npm-debug.log 4 | .npmrc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Contributors to the Electron project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electron ChromeDriver 2 | 3 | [![Test](https://github.com/electron/chromedriver/actions/workflows/test.yml/badge.svg)](https://github.com/electron/chromedriver/actions/workflows/test.yml) 4 | [![npm:](https://img.shields.io/npm/v/electron-chromedriver.svg)](https://www.npmjs.com/package/electron-chromedriver) 5 |
6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) 7 | [![license:mit](https://img.shields.io/badge/license-mit-blue.svg)](https://opensource.org/licenses/MIT) 8 |
9 | [![dependencies:?](https://img.shields.io/npm/dm/electron-chromedriver.svg)](https://www.npmjs.com/package/electron-chromedriver) 10 | 11 | Simple node module to download the [ChromeDriver](https://sites.google.com/corp/chromium.org/driver/) 12 | version for [Electron](https://electronjs.org). 13 | 14 | The major version of this library tracks the major version of the Electron 15 | versions released. So if you are using Electron `2.0.x` you would want to use 16 | an `electron-chromedriver` dependency of `~2.0.0` in your `package.json` file. 17 | 18 | This library is used by [spectron](https://github.com/electron/spectron). 19 | 20 | ## Using 21 | 22 | ```sh 23 | npm install --save-dev electron-chromedriver 24 | chromedriver -h 25 | ``` 26 | 27 | ## Custom Mirror 28 | 29 | You can set the `ELECTRON_MIRROR` or [`NPM_CONFIG_ELECTRON_MIRROR`](https://docs.npmjs.com/misc/config#environment-variables) 30 | environment variables to use a custom base URL for downloading ChromeDriver zips. 31 | 32 | ```sh 33 | # Electron mirror for China 34 | ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/" 35 | 36 | # Local mirror 37 | # Example of requested URL: http://localhost:8080/1.2.0/chromedriver-v2.21-darwin-x64.zip 38 | ELECTRON_MIRROR="http://localhost:8080/" 39 | ``` 40 | 41 | ## Overriding the version downloaded 42 | 43 | The version downloaded can be overriden by setting the `ELECTRON_CUSTOM_VERSION` environment variable. 44 | -------------------------------------------------------------------------------- /chromedriver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const ChildProcess = require('child_process') 4 | const path = require('path') 5 | 6 | const command = path.join(__dirname, 'bin', 'chromedriver') 7 | const args = process.argv.slice(2) 8 | const options = { 9 | cwd: process.cwd(), 10 | env: process.env, 11 | stdio: 'inherit' 12 | } 13 | 14 | const chromeDriverProcess = ChildProcess.spawn(command, args, options) 15 | 16 | chromeDriverProcess.on('close', code => { 17 | if (code !== null && code !== 0) { 18 | throw new Error(`Chromedriver exited with error code: ${code}`) 19 | } 20 | }) 21 | 22 | chromeDriverProcess.on('error', error => { throw new Error(error) }) 23 | 24 | const killChromeDriver = () => { 25 | try { 26 | chromeDriverProcess.kill() 27 | } catch (ignored) {} 28 | } 29 | 30 | process.on('exit', killChromeDriver) 31 | process.on('SIGTERM', killChromeDriver) 32 | -------------------------------------------------------------------------------- /download-chromedriver.js: -------------------------------------------------------------------------------- 1 | const { promises: fs } = require('fs') 2 | const path = require('path') 3 | const { downloadArtifact } = require('@electron/get') 4 | const extractZip = require('extract-zip') 5 | const versionToDownload = require('./package').version 6 | 7 | function download (version) { 8 | return downloadArtifact({ 9 | version, 10 | artifactName: 'chromedriver', 11 | force: process.env.force_no_cache === 'true', 12 | cacheRoot: process.env.electron_config_cache, 13 | platform: process.env.npm_config_platform, 14 | arch: process.env.npm_config_arch, 15 | rejectUnauthorized: process.env.npm_config_strict_ssl === 'true', 16 | quiet: ['info', 'verbose', 'silly', 'http'].indexOf(process.env.npm_config_loglevel) === -1 17 | }) 18 | } 19 | 20 | async function attemptDownload (version) { 21 | // Fall back to latest stable if there is not a stamped version, for tests 22 | if (version === '0.0.0-development') { 23 | if (!process.env.ELECTRON_CHROMEDRIVER_STABLE_FALLBACK) { 24 | console.log('WARNING: chromedriver in development needs the environment variable ELECTRON_CHROMEDRIVER_STABLE_FALLBACK set') 25 | process.exit(1) 26 | } 27 | 28 | const { ElectronVersions } = require('@electron/fiddle-core') 29 | const versions = await ElectronVersions.create(undefined, { ignoreCache: true }) 30 | version = versions.latestStable.version 31 | } 32 | 33 | try { 34 | const targetFolder = path.join(__dirname, 'bin') 35 | const zipPath = await download(version) 36 | await extractZip(zipPath, { dir: targetFolder }) 37 | const platform = process.env.npm_config_platform || process.platform 38 | if (platform !== 'win32') { 39 | await fs.chmod(path.join(targetFolder, 'chromedriver'), 0o755) 40 | } 41 | } catch (err) { 42 | // attempt to fall back to semver minor 43 | const parts = version.split('.') 44 | const baseVersion = `${parts[0]}.${parts[1]}.0` 45 | 46 | // don't recurse infinitely 47 | if (baseVersion === version) { 48 | throw err 49 | } else { 50 | await attemptDownload(baseVersion) 51 | } 52 | } 53 | } 54 | 55 | attemptDownload(versionToDownload) 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-chromedriver", 3 | "version": "0.0.0-development", 4 | "description": "Electron ChromeDriver", 5 | "repository": "https://github.com/electron/chromedriver", 6 | "bin": { 7 | "chromedriver": "./chromedriver.js" 8 | }, 9 | "files": [ 10 | "./chromedriver.js", 11 | "./download-chromedriver.js" 12 | ], 13 | "scripts": { 14 | "install": "node ./download-chromedriver.js", 15 | "lint": "standard --fix", 16 | "test": "standard && mocha" 17 | }, 18 | "engines": { 19 | "node": ">=10.12.0" 20 | }, 21 | "author": "Kevin Sawicki", 22 | "license": "MIT", 23 | "dependencies": { 24 | "@electron/get": "^2.0.1", 25 | "extract-zip": "^2.0.0" 26 | }, 27 | "devDependencies": { 28 | "@continuous-auth/client": "^2.3.0", 29 | "@electron/fiddle-core": "^1.3.3", 30 | "mocha": "^10.1.0", 31 | "standard": "^13.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /script/publish.js: -------------------------------------------------------------------------------- 1 | // Publish the package in the CWD with an OTP code from CFA 2 | const { getOtp } = require('@continuous-auth/client') 3 | const { spawnSync } = require('child_process') 4 | 5 | async function publish () { 6 | const { status } = spawnSync('npm', ['publish', '--provenance', '--otp', await getOtp()]) 7 | process.exit(status) 8 | } 9 | 10 | publish() 11 | -------------------------------------------------------------------------------- /script/update-version.js: -------------------------------------------------------------------------------- 1 | const { promises: fs } = require('fs') 2 | const path = require('path') 3 | 4 | const versionFormat = /^(\d+\.)(\d+\.)(\d+)$/ 5 | 6 | const normalizeVersion = (version = '') => { 7 | return version.startsWith('v') ? version.slice(1) : version 8 | } 9 | 10 | async function updateVersion () { 11 | const version = normalizeVersion(process.argv[2]) 12 | if (!versionFormat.test(version)) { 13 | console.error(`Unsupported version ${version} - only major, minor, and patch releases are currently supported`) 14 | return 15 | } 16 | 17 | const PJ_PATH = path.join(__dirname, '..', 'package.json') 18 | const pj = require(PJ_PATH) 19 | 20 | const PJLOCK_PATH = path.join(__dirname, '..', 'package-lock.json') 21 | const pjLock = require(PJLOCK_PATH) 22 | 23 | try { 24 | pj.version = version 25 | await fs.writeFile(PJ_PATH, JSON.stringify(pj, null, 2)) 26 | console.log(`Updated package.json version to ${version}`) 27 | 28 | pjLock.version = version 29 | await fs.writeFile(PJLOCK_PATH, JSON.stringify(pjLock, null, 2)) 30 | console.log(`Updated package-lock.json version to ${version}`) 31 | } catch (e) { 32 | console.error('Failed to update chromedriver version: ', e) 33 | process.exit(1) 34 | } 35 | } 36 | 37 | updateVersion() 38 | -------------------------------------------------------------------------------- /test/chromedriver-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const { spawnSync } = require('child_process') 3 | const path = require('path') 4 | const { version: releaseVersion } = require('../package') 5 | const { ElectronVersions } = require('@electron/fiddle-core') 6 | 7 | const describe = global.describe 8 | const it = global.it 9 | 10 | function outputHasExpectedVersion (output, version) { 11 | return output.toString().includes(version) 12 | } 13 | 14 | describe('chromedriver binary', function () { 15 | this.timeout(10000) 16 | 17 | it('launches successfully', async function () { 18 | // Skip when this package is not properly configured for an Electron release 19 | if (releaseVersion === '0.0.0-development') { 20 | this.skip() 21 | } 22 | 23 | // Get the expected release information for this release 24 | const versions = await ElectronVersions.create() 25 | const expectedInfo = versions.getReleaseInfo(releaseVersion) 26 | 27 | const { chrome: expectedChromeVersion } = expectedInfo 28 | 29 | // Invoke chormedriver with the flag to output the version 30 | const { stdout, stderr } = spawnSync(process.execPath, [ 31 | path.join(__dirname, '..', 'chromedriver.js'), 32 | '-v' 33 | ]) 34 | 35 | assert( 36 | outputHasExpectedVersion(stdout, expectedChromeVersion) || outputHasExpectedVersion(stderr, expectedChromeVersion), 37 | `Did not find expected Chromium version: ${expectedChromeVersion}\nstdout:\n---\n${stdout}\n---\nstderr:\n${stderr}\n---` 38 | ) 39 | }) 40 | }) 41 | --------------------------------------------------------------------------------