├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── docker ├── Dockerfile ├── docker-compose.yml ├── entrypoint.sh └── import_cert.sh ├── jest.config.js ├── lib ├── defaults.js └── index.js ├── package-lock.json ├── package.json ├── src └── utils.js └── tests ├── run-container └── run-container.test.js └── unit └── chromium-revision-version.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 3 | "rules": { 4 | "prettier/prettier": [ 5 | "error", 6 | { 7 | "singleQuote": true, 8 | "tabWidth": 4 9 | } 10 | ], 11 | "no-console": "off" 12 | }, 13 | "env": { 14 | "node": true, 15 | "jest": true, 16 | "es6": true 17 | }, 18 | "parserOptions": { 19 | "ecmaVersion": 8 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set 2 | * text=auto 3 | 4 | # Require Unix line endings 5 | * text eol=lf 6 | 7 | # Denote all files that are truly binary and should not be modified. 8 | *.png binary 9 | *.zip binary 10 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Tests 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm test 29 | env: 30 | CI: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | .npm 5 | .env 6 | npm-debug.log 7 | node_modules 8 | wsEndpoint -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "useTabs": false, 4 | "tabWidth": 4, 5 | "singleQuote": true, 6 | "endOfLine": "auto" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gideon Pyzer 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 | # docker-chromium 2 | 3 | ![Tests](https://github.com/gidztech/docker-chromium/workflows/Tests/badge.svg) 4 | **Node library for controlling a Chromium instance running in a Docker container** 5 | 6 | [![NPM](https://nodei.co/npm/docker-chromium.png)](https://www.npmjs.com/package/docker-chromium) 7 | 8 | ## Installation 9 | 10 | **Requirements:** 11 | 12 | - [`Docker`](https://docs.docker.com/install/) 13 | 14 | ``` 15 | npm install --save docker-chromium 16 | ``` 17 | 18 | ## Basic Usage 19 | 20 | ```javascript 21 | const { 22 | dockerSetChromiumConfig, 23 | dockerRunChromium, 24 | dockerShutdownChromium 25 | } = require("docker-chromium"); 26 | 27 | (async () => { 28 | await dockerSetChromiumConfig({ 29 | revision: "123456" 30 | flags: [' -–ignore-certificate-errors'] 31 | }); 32 | const webSocketUri = await dockerRunChromium(); 33 | // do some other stuff... 34 | await dockerShutdownChromium(); 35 | })(); 36 | ``` 37 | 38 | Default is trying to connect to Chromium 5 times with a 500 milisecond interval between each attempt. You can customize timeout/attempts by passing arguments to `dockerRunChromium`: 39 | 40 | ```javascript 41 | // ... 42 | const webSocketUri = await dockerRunChromium({ 43 | maxAttempts: 10, 44 | retryInterval: 5000 // 5 seconds 45 | }); 46 | ``` 47 | 48 | Or by defining environment variables `DOCKER_CHROMIUM_MAX_ATTEMPTS` and `DOCKER_CHROMIUM_RETRY_INTERVAL`. Passing arguments to `dockerRunChromium` takes precedence over environment variables. 49 | 50 | ## How it works 51 | 52 | `docker-chromium` pulls a pre-built Docker image running a version of Chromium specified by you from a Docker Hub repository. You can then fetch the WebSocket URI to connect to the instance in your own application. If the pre-built image is unavailable or corrupt (rare case), a backup mechanism is in place, which builds the image from scratch locally instead. 53 | 54 | ### Update 55 | 56 | Due to Ubuntu 14.04 LTS transitioning to ESM support, we have had to upgrade the Ubuntu version to 18.04 LTS. The Dockerfile used in the pre-built version in Docker Hub remains on the old version. Until this is changed, we have to disable this option for the time being. 57 | 58 | ## API 59 | 60 | ### dockerSetChromiumConfig 61 | 62 | Function which is used for the configuration of Chromium, before running it with `dockerRunChromium`. 63 | 64 | ##### Example of usage 65 | 66 | ```javascript 67 | await dockerSetChromiumConfig({ 68 | revision: '123456', 69 | flags: [' -–ignore-certificate-errors'] 70 | }); 71 | ``` 72 | 73 | ##### Arguments 74 | 75 | - `revision: string` 76 | - **Required** 77 | - Describes version number for Chromium 78 | - `flags: string[]` 79 | - _Optional_ 80 | - _Defaults_ to `process.env.CHROMIUM_ADDITIONAL_ARGS` 81 | - Describes command line flags that Chromium accepts. 82 | - `downloadHost: string` 83 | 84 | - _Optional_ 85 | - _Defaults_ to `process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host || process.env.npm_package_config_puppeteer_download_host || 'https://storage.googleapis.com'` 86 | - Describes host address for downloading of Chromium. 87 | - Describes only the beginning of address, like this rule: `$CHROMIUM_DOWNLOAD_HOST/chromium-browser-snapshots/Linux_x64/$REV/chrome-linux.zip` - `$CHROMIUM_DOWNLOAD_HOST` describes `downloadHost` argument 88 | - Example: If we run `dockerSetChromiumConfig({downloadHost: 'https://internal.service.com, revision: 99999})`, it means that Chromium snapshot will be downloaded from _https://internal.service.com/chromium-browser-snapshots/Linux_x64/99999/chrome-linux.zip_ 89 | 90 | - `useClosestUbuntuMirror: boolean` 91 | - _Optional_ 92 | - _Defaults_ to `process.env.USE_CLOSEST_UBUNTU_MIRROR || process.env.npm_config_use_closest_ubuntu_mirror || process.env.npm_package_config_use_closest_ubuntu_mirror|| false` 93 | - Flag for setting whether Ubuntu should use the default mirror for fetching packages, or pick the closest mirror depending on location 94 | 95 | ### dockerRunChromium 96 | 97 | Function which is used to build and run the Docker container. 98 | 99 | ##### Example of usage 100 | 101 | ```javascript 102 | const webSocketUri = await dockerRunChromium({ 103 | maxAttempts: 10, 104 | retryInterval: 5000 105 | }); 106 | ``` 107 | 108 | ##### Arguments 109 | 110 | - `maxAttempts: number` 111 | - _Optional_ 112 | - _Defaults_ to 5 113 | - Describes number of attempts to connect to Chromium in the launched container (sometimes it fails). 114 | - `retryInterval: number` 115 | - _Optional_ 116 | - _Defaults_ to 500 117 | - Describes number of milliseconds between attempts to connect to Chromium in the launched container. 118 | 119 | ##### Returns 120 | 121 | - `Promise` 122 | - Returns web socket URI to the launched Chromium in the container. 123 | - By using this URI we can connect to Chromium and control it. 124 | 125 | ### dockerShutdownChromium 126 | 127 | Function which is used to shutdown the launched Docker container. 128 | 129 | ##### Example of usage 130 | 131 | ```javascript 132 | await dockerShutdownChromium(); 133 | ``` 134 | 135 | ## Contributors 136 | 137 | - [**gidztech**](https://github.com/gidztech) 138 | - [_Fiszcz_](https://github.com/Fiszcz) 139 | - [_luiz_](https://github.com/luiz) 140 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current' 8 | } 9 | } 10 | ] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | EXPOSE 9222 4 | 5 | ARG USE_CLOSEST_UBUNTU_MIRROR 6 | ARG CHROMIUM_ADDITIONAL_ARGS 7 | ENV CHROMIUM_ADDITIONAL_ARGS=${CHROMIUM_ADDITIONAL_ARGS} 8 | 9 | ADD entrypoint.sh /usr/bin/entrypoint.sh 10 | ADD import_cert.sh /usr/bin/ 11 | 12 | # https://www.authentise.com/post/tidy-docker-environment 13 | RUN if [ "$USE_CLOSEST_UBUNTU_MIRROR" = "true" ]; then \ 14 | echo "--> Using closest Ubuntu" && \ 15 | echo "deb mirror://mirrors.ubuntu.com/mirrors.txt focal main restricted universe multiverse" > /etc/apt/sources.list && \ 16 | echo "deb mirror://mirrors.ubuntu.com/mirrors.txt focal-updates main restricted universe multiverse" >> /etc/apt/sources.list && \ 17 | echo "deb mirror://mirrors.ubuntu.com/mirrors.txt focal-security main restricted universe multiverse" >> /etc/apt/sources.list; \ 18 | else \ 19 | echo "--> Using default Ubuntu mirror"; \ 20 | fi 21 | 22 | RUN DEBIAN_FRONTEND="noninteractive" apt-get -qqy update && DEBIAN_FRONTEND="noninteractive" apt-get -qqy install libnss3 libnss3-tools libxss1 libfontconfig1 wget ca-certificates apt-transport-https inotify-tools unzip \ 23 | libpangocairo-1.0-0 libx11-xcb-dev libxcomposite-dev libxcursor1 libxdamage1 libxi6 libgconf-2-4 libxtst6 libcups2-dev \ 24 | libxss-dev libxrandr-dev libasound2-dev libatk1.0-dev libgtk-3-dev ttf-ancient-fonts chromium-codecs-ffmpeg-extra libappindicator3-1 \ 25 | iputils-ping iproute2 curl dumb-init \ 26 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* && chmod +x /usr/bin/entrypoint.sh 27 | 28 | ARG CHROMIUM_REVISION 29 | ARG CHROMIUM_DOWNLOAD_HOST 30 | 31 | RUN wget --no-check-certificate -q -O chrome.zip $CHROMIUM_DOWNLOAD_HOST/chromium-browser-snapshots/Linux_x64/$CHROMIUM_REVISION/chrome-linux.zip \ 32 | && unzip chrome.zip \ 33 | && rm chrome.zip \ 34 | && ln -s $PWD/chrome-linux/chrome /usr/bin/google-chrome-unstable 35 | 36 | RUN google-chrome-unstable --version 37 | 38 | RUN mkdir /data 39 | VOLUME /data 40 | ENV HOME=/data DEBUG_ADDRESS=0.0.0.0 DEBUG_PORT=9222 41 | 42 | ENTRYPOINT ["/usr/bin/dumb-init", "--"] 43 | CMD ["/usr/bin/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | chromium: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile 7 | shm_size: "1gb" 8 | ports: 9 | - "9222:9222" 10 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Make sure there's a host entry 4 | HOST_DOMAIN="host.docker.internal" 5 | DOCKER_IP="$(getent hosts host.docker.internal | awk '{ print $1 }')" 6 | echo $DOCKER_IP " " $HOST_DOMAIN >> /etc/hosts 7 | 8 | ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1 9 | if [ $? -ne 0 ]; then 10 | # Try using default interface 11 | DOCKER_IP="$(ip -4 route show default | cut -d' ' -f3)" 12 | ping -q -c1 $DOCKER_IP > /dev/null 2>&1 13 | if [ $? -eq 0 ]; then 14 | # Default interface was good so patch hosts 15 | echo $DOCKER_IP " " $HOST_DOMAIN >> /etc/hosts 16 | else 17 | # Try eth0 instead and then patch hosts 18 | DOCKER_IP="$(ip addr show eth0 | grep 'inet ' | awk '{ print $2}' | cut -d'/' -f1)" 19 | echo $DOCKER_IP " " $HOST_DOMAIN >> /etc/hosts 20 | fi 21 | fi 22 | 23 | # Taken from https://github.com/alpeware/chrome-headless-trunk/blob/master/start.sh 24 | ### 25 | # Run the NSSDB updating utility in background 26 | import_cert.sh $HOME & 27 | CHROME_ARGS="--disable-gpu --headless --no-sandbox --remote-debugging-address=$DEBUG_ADDRESS --remote-debugging-port=$DEBUG_PORT --user-data-dir=/data --disable-dev-shm-usage" 28 | 29 | CHROMIUM_ADDITIONAL_ARGS=$(echo $CHROMIUM_ADDITIONAL_ARGS | tr ',' ' ') 30 | 31 | # Start Chrome 32 | sh -c "/usr/bin/google-chrome-unstable $CHROME_ARGS $CHROMIUM_ADDITIONAL_ARGS" 33 | ### 34 | -------------------------------------------------------------------------------- /docker/import_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Taken from https://github.com/alpeware/chrome-headless-stable 4 | 5 | # Script that listen for changes in 'certificates' subdir of 'BASEDIR' 6 | # and updates the NSSDB when PEM encoded certificates are updated. 7 | 8 | if [ -z "$1" ]; then 9 | echo "Usage: import_cert.sh BASEDIR" 10 | exit 1 11 | fi 12 | 13 | BASEDIR=$1 14 | CERTS=$BASEDIR/certificates 15 | NSSDB=$BASEDIR/.pki/nssdb 16 | 17 | echo "Looking for CA certificate in $CERTS" 18 | 19 | mkdir -p $CERTS 20 | mkdir -p $NSSDB 21 | 22 | function add_cert { 23 | file=$1 24 | basename=$(basename $file) 25 | certname=${basename%%.*} 26 | suffix=${basename##*.} 27 | 28 | if [ "pem" = "$suffix" ]; then 29 | if certutil -d sql:$NSSDB -A -t "CT,C,C" -n $certname -i $file; then 30 | echo "Certificate $pem imported" 31 | fi 32 | fi 33 | } 34 | 35 | certutil -d sql:$NSSDB --empty-password -N -f /dev/null 2> /dev/null 36 | echo "Keystore created" 37 | 38 | for pem in `ls $CERTS/*.pem 2> /dev/null`; do 39 | add_cert $pem 40 | done 41 | 42 | while true; do 43 | pem=`inotifywait -q -e create -e modify -e moved_to --format '%w%f' $CERTS` 44 | add_cert $pem 45 | done 46 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | silent: true, 3 | resetMocks: true 4 | }; 5 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DEFAULT_CHROMIUM_REVISION: 754094, 3 | DEFAULT_CHROMIUM_DOWNLOAD_HOST: 'https://storage.googleapis.com', 4 | DEFAULT_USE_CLOSEST_UBUNTU_MIRROR: false 5 | }; 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native'); 2 | const path = require('path'); 3 | 4 | require('colors'); 5 | 6 | const { CONSOLE_PREFIX, runCommand } = require('../src/utils'); 7 | 8 | const { 9 | DEFAULT_CHROMIUM_REVISION, 10 | DEFAULT_CHROMIUM_DOWNLOAD_HOST, 11 | DEFAULT_USE_CLOSEST_UBUNTU_MIRROR 12 | } = require('./defaults'); 13 | 14 | const dockerComposePath = path.join(__dirname, '../docker/docker-compose.yml'); 15 | 16 | let serviceToBuild = 'chromium'; 17 | let chromiumAdditionalArgs; 18 | let chromiumDownloadHost; 19 | let chromiumRevision; 20 | let shouldUseClosestUbuntuMirror; 21 | 22 | const dockerBuild = async () => { 23 | try { 24 | console.log(`${CONSOLE_PREFIX} Building Docker image...`.green); 25 | await runCommand('docker-compose', [ 26 | '-f', 27 | `"${dockerComposePath}"`, 28 | 'build', 29 | `--build-arg CHROMIUM_ADDITIONAL_ARGS="${chromiumAdditionalArgs}"`, 30 | `--build-arg CHROMIUM_DOWNLOAD_HOST=${chromiumDownloadHost}`, 31 | `--build-arg CHROMIUM_REVISION=${chromiumRevision || 32 | DEFAULT_CHROMIUM_REVISION}`, 33 | `--build-arg USE_CLOSEST_UBUNTU_MIRROR=${shouldUseClosestUbuntuMirror || 34 | DEFAULT_USE_CLOSEST_UBUNTU_MIRROR}`, 35 | '--pull', 36 | serviceToBuild 37 | ]); 38 | console.log(`${CONSOLE_PREFIX} Successfully built Docker image`.green); 39 | } catch (error) { 40 | throw new Error( 41 | `${CONSOLE_PREFIX} Failed to build Docker image \n\nInternal Error: \n\n${error}` 42 | ); 43 | } 44 | }; 45 | 46 | const dockerUp = async () => { 47 | try { 48 | console.log(`${CONSOLE_PREFIX} Starting Docker container...`.green); 49 | await runCommand('docker-compose', [ 50 | '-f', 51 | `"${dockerComposePath}"`, 52 | 'up', 53 | '-d', 54 | serviceToBuild 55 | ]); 56 | console.log( 57 | `${CONSOLE_PREFIX} Successfully started Docker container`.green 58 | ); 59 | } catch (error) { 60 | throw new Error( 61 | `${CONSOLE_PREFIX} Failed to start Docker container \n\nInternal Error: \n\n${error}` 62 | ); 63 | } 64 | }; 65 | 66 | const dockerDown = async () => { 67 | try { 68 | console.log( 69 | `${CONSOLE_PREFIX} Shutting down Docker container...`.green 70 | ); 71 | await runCommand('docker-compose', [ 72 | '-f', 73 | `"${dockerComposePath}"`, 74 | 'down' 75 | ]); 76 | console.log( 77 | `${CONSOLE_PREFIX} Successfully shut down Docker container`.green 78 | ); 79 | } catch (error) { 80 | throw new Error( 81 | `${CONSOLE_PREFIX} Failed to shut down Docker container \n\nInternal Error: \n\n${error}` 82 | ); 83 | } 84 | }; 85 | 86 | const contactChromium = async ({ config, maxAttempts, retryInterval }) => { 87 | let count = 1; 88 | console.log(`${CONSOLE_PREFIX} Contacting Chromium in container...`.green); 89 | 90 | async function tryRequest() { 91 | try { 92 | return await request(config); 93 | } catch (e) { 94 | count += 1; 95 | if (count <= maxAttempts) { 96 | return new Promise(resolve => { 97 | setTimeout(async () => { 98 | console.log( 99 | `${CONSOLE_PREFIX} Attempt #${count}`.yellow 100 | ); 101 | resolve(await tryRequest()); 102 | }, retryInterval); 103 | }); 104 | } 105 | console.log( 106 | `${CONSOLE_PREFIX} Max number of attempts exceeded. I'm giving up!` 107 | .red 108 | ); 109 | throw e; 110 | } 111 | } 112 | 113 | return tryRequest(); 114 | }; 115 | 116 | const dockerConfig = ({ 117 | flags, 118 | revision, 119 | downloadHost, 120 | useClosestUbuntuMirror 121 | }) => { 122 | shouldUseClosestUbuntuMirror = 123 | useClosestUbuntuMirror || 124 | process.env.USE_CLOSEST_UBUNTU_MIRROR || 125 | process.env.npm_config_use_closest_ubuntu_mirror || 126 | process.env.npm_package_config_use_closest_ubuntu_mirror; 127 | console.log( 128 | `${CONSOLE_PREFIX} Setting Ubuntu to use ${ 129 | useClosestUbuntuMirror ? 'closest' : 'default' 130 | } mirror for packages...`.green 131 | ); 132 | 133 | chromiumDownloadHost = 134 | downloadHost || 135 | process.env.PUPPETEER_DOWNLOAD_HOST || 136 | process.env.npm_config_puppeteer_download_host || 137 | process.env.npm_package_config_puppeteer_download_host || 138 | DEFAULT_CHROMIUM_DOWNLOAD_HOST; 139 | 140 | console.log( 141 | `${CONSOLE_PREFIX} Setting Chromium download host to ${chromiumDownloadHost}...` 142 | .green 143 | ); 144 | 145 | chromiumAdditionalArgs = flags || process.env.CHROMIUM_ADDITIONAL_ARGS; 146 | if (chromiumAdditionalArgs) { 147 | console.log( 148 | `${CONSOLE_PREFIX} Setting Chromium flags to ${chromiumAdditionalArgs}...` 149 | .green 150 | ); 151 | } 152 | 153 | if (revision) { 154 | console.log( 155 | `${CONSOLE_PREFIX} Setting Chromium version to rev-${revision}...` 156 | .green 157 | ); 158 | 159 | chromiumRevision = revision; 160 | } 161 | }; 162 | 163 | const dockerRun = async ({ maxAttempts, retryInterval } = {}) => { 164 | maxAttempts = maxAttempts || process.env.DOCKER_CHROMIUM_MAX_ATTEMPTS || 5; 165 | retryInterval = 166 | retryInterval || process.env.DOCKER_CHROMIUM_RETRY_INTERVAL || 500; 167 | 168 | await dockerBuild(); 169 | await dockerUp(); 170 | 171 | const res = await contactChromium({ 172 | config: { 173 | uri: `http://localhost:9222/json/version`, 174 | json: true, 175 | resolveWithFullResponse: true 176 | }, 177 | maxAttempts, 178 | retryInterval 179 | }); 180 | 181 | const webSocketUri = res.body.webSocketDebuggerUrl; 182 | console.log( 183 | `${CONSOLE_PREFIX} Connected to WebSocket URL: ${webSocketUri}`.green 184 | ); 185 | 186 | return webSocketUri; 187 | }; 188 | 189 | module.exports = { 190 | dockerSetChromiumConfig: dockerConfig, 191 | dockerBuildContainer: dockerBuild, 192 | dockerRunChromium: dockerRun, 193 | dockerShutdownChromium: dockerDown 194 | }; 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-chromium", 3 | "version": "1.4.2", 4 | "description": "Node library for controlling a Chromium instance running in a Docker container", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "lint": "eslint **/*.js --fix --ignore-pattern node_modules", 8 | "prettier": "prettier --write --single-quote --tab-width=4 */**.js", 9 | "precommit": "lint-staged", 10 | "test": "jest" 11 | }, 12 | "lint-staged": { 13 | "*.js": [ 14 | "prettier --write --single-quote --tab-width=4", 15 | "git add" 16 | ] 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/gidztech/docker-chromium.git" 21 | }, 22 | "author": "Gideon Pyzer", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/gidztech/docker-chromium/issues" 26 | }, 27 | "homepage": "https://github.com/gidztech/docker-chromium#readme", 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.9.0", 30 | "babel-jest": "^25.2.3", 31 | "eslint": "^5.16.0", 32 | "eslint-config-prettier": "^3.6.0", 33 | "eslint-plugin-prettier": "^3.1.2", 34 | "husky": "^1.3.1", 35 | "jest": "^25.2.3", 36 | "lint-staged": "^8.2.1", 37 | "prettier": "^1.19.1" 38 | }, 39 | "dependencies": { 40 | "colors": "^1.4.0", 41 | "request": "^2.88.2", 42 | "request-promise-native": "^1.0.8" 43 | }, 44 | "directories": { 45 | "lib": "lib" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const exec = promisify(require('child_process').exec); 3 | 4 | module.exports = { 5 | runCommand: async (command, args) => { 6 | const { stdout, stderr } = await exec(`${command} ${args.join(' ')}`); 7 | 8 | console.log(stdout); 9 | console.log(stderr); 10 | 11 | return stdout; 12 | }, 13 | CONSOLE_PREFIX: 'Docker Chromium:' 14 | }; 15 | -------------------------------------------------------------------------------- /tests/run-container/run-container.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | dockerSetChromiumConfig, 3 | dockerRunChromium, 4 | dockerShutdownChromium 5 | } = require('../../lib/index'); 6 | 7 | describe('runContainer', () => { 8 | afterAll(async () => { 9 | await dockerShutdownChromium(); 10 | }); 11 | 12 | it('runs container and provides websocket uri', async () => { 13 | dockerSetChromiumConfig({ 14 | flags: [' -–ignore-certificate-errors'], 15 | revision: 754306 16 | }); 17 | 18 | const webSocketUri = await dockerRunChromium(); 19 | 20 | expect(webSocketUri).toContain('ws://'); 21 | }, 300000); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/unit/chromium-revision-version.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | dockerBuildContainer, 3 | dockerSetChromiumConfig, 4 | dockerShutdownChromium 5 | } from '../../lib'; 6 | import { runCommand } from '../../src/utils'; 7 | import { DEFAULT_CHROMIUM_REVISION } from '../../lib/defaults'; 8 | 9 | jest.mock('../../src/utils'); 10 | 11 | describe('index', () => { 12 | it('should use default chromium revision version if we do not pass any revision to config function', async () => { 13 | dockerSetChromiumConfig({}); 14 | 15 | await dockerBuildContainer(); 16 | 17 | const expectedChromiumRevisionBuildArg = `--build-arg CHROMIUM_REVISION=${DEFAULT_CHROMIUM_REVISION}`; 18 | expect(runCommand.mock.calls[0][1]).toContain( 19 | expectedChromiumRevisionBuildArg 20 | ); 21 | }); 22 | 23 | it('should set chromium revision and then run proper command to build docker with given revision', async () => { 24 | dockerSetChromiumConfig({ revision: 999 }); 25 | 26 | await dockerBuildContainer(); 27 | 28 | expect(runCommand.mock.calls[0][1]).toContain( 29 | '--build-arg CHROMIUM_REVISION=999' 30 | ); 31 | }); 32 | }); 33 | --------------------------------------------------------------------------------