16

Using:

Distributor ID: Debian
Description:    Debian GNU/Linux 10 (buster)
Release:    10
Codename:   buster

Google is now recommending that people use Chrome for Testing for their test automation instead of commercial version of Chrome and I can't find a way to make it work. This is originally how we did it in our pipeline:

curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list
apt-get -y update
apt-get -y install google-chrome-stable

But now using the new api for Chrome for Testing this is what I'm using in our shell script:

LATEST_CHROME_JSON=$(curl -s https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json | jq '.channels.Stable')
LATEST_CHROME_URL=$(echo "$LATEST_CHROME_JSON" | jq -r '.downloads.chrome[] | select(.platform == "linux64") | .url')
wget -N "$LATEST_CHROME_URL" -P ~/
unzip ~/chrome-linux64.zip -d ~/
rm ~/chrome-linux64.zip
mkdir -p /opt/chrome
mv ~/chrome-linux64/* /opt/chrome/
rm -r ~/chrome-linux64
ln -s /opt/chrome/chrome /usr/local/bin/chrome
chmod +x /opt/chrome/chrome
chrome --version

Everytime I use chrome --version the pipeline fails because some dependency or another is missing. How come I didn't need any of these dependencies before? And how do I have it print out all the required dependencies instead of erroring out one by one? Right now I keep adding a new dependency to install, commit and push, run my pipeline, only to see a brand new error and a brand new dependency is needed.

The dependencies I'm installing:

apt-get update
apt-get install -y unzip openjdk-8-jre-headless
apt-get install -y xvfb libxi6 libgconf-2-4 jq libjq1 libonig5 #xorg apt-get install -y jq libjq1 libonig5
apt-get install -y libnss3
apt-get install -y libatk1.0-0
apt-get install -y libatk-bridge2.0-0

I had to separate them because openjdk-8-jre-headless kept failing because for some reason it's missing or something. I remember getting this from this tutorial on installing chrome: https://gist.github.com/buttreygoodness/09e26d7f21eb5b95a4229658a7a9b321

As I post this, my pipeline just failed again this time with error

chrome: error while loading shared libraries: libcups.so.2: cannot open shared object file: No such file or directory

This is getting frustrating. There has to be a better way to install chrome from a zip file like this. I don't understand why they couldn't just make a .deb file like they normally do

2
  • I installed it with reference to this blog. How can I get Chrome for Testing binaries? Commented Jul 20, 2023 at 20:49
  • 2
    we're not using node/npm. We would need to download and install node on our testing machines purely for chrome for testing and nothing else. Commented Jul 21, 2023 at 2:18

3 Answers 3

15

Finally worked by using these dependencies

apt-get install -y unzip xvfb libxi6 libgconf-2-4 jq libjq1 libonig5 libxkbcommon0 libxss1 libglib2.0-0 libnss3 \
  libfontconfig1 libatk-bridge2.0-0 libatspi2.0-0 libgtk-3-0 libpango-1.0-0 libgdk-pixbuf2.0-0 libxcomposite1 \
  libxcursor1 libxdamage1 libxtst6 libappindicator3-1 libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libxfixes3 \
  libdbus-1-3 libexpat1 libgcc1 libnspr4 libgbm1 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxext6 \
  libxrandr2 libxrender1 gconf-service ca-certificates fonts-liberation libappindicator1 lsb-release xdg-utils

LATEST_CHROME_RELEASE=$(curl -s https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json | jq '.channels.Stable')
LATEST_CHROME_URL=$(echo "$LATEST_CHROME_RELEASE" | jq -r '.downloads.chrome[] | select(.platform == "linux64") | .url')
wget -N "$LATEST_CHROME_URL" -P ~/
unzip ~/chrome-linux64.zip -d ~/
mv ~/chrome-linux64 ~/chrome
ln -s ~/chrome/chrome /usr/local/bin/chrome
chmod +x ~/chrome
rm ~/chrome-linux64.zip
Sign up to request clarification or add additional context in comments.

6 Comments

You can use apt show google-chrome to list all dependencies of the Google Chrome .deb package. The dependencies for CfT should be mostly the same as those for Google Chrome.
I feel this is not sustainable model. Manually installing os packages feels like 2002 to me. Ideally there should be beeter way (package manager way) to install CFT from Google.
Agree with Amar that there should be a package manager way of doing this, its nice we can always install the latest chrome(driver) versions but the dependencies not being included (or referenced) feels like a step back
Thank you, thank you, thank you! I've been pulling my hair out over this! Def needs a better installation method for sure.
There's a feature request for this in the Chrome for Testing bug tracker.
|
0

I was having the same issue using chrome and chrome driver in a container. This is to be used for a locust based real browser performance test. I have come to find two solutions to this.

First solution is to use the npm package to install the desired chrome version. For instance a Dockerfile could look like the following:

# Install npm
RUN apt-get update && apt-get install -y npm \
    && rm -rf /var/lib/apt/lists/*
    
# Install a specific version of Google Chrome
ARG CHROME_DRIVER_VERSION=132.0.6834.110

# Install Puppeteer Chrome browser and driver
RUN npx @puppeteer/browsers install chrome@${CHROME_DRIVER_VERSION} --install-deps
Run npx @puppeteer/browsers install chromedriver@${CHROME_DRIVER_VERSION}

The --install-deps option installs chrome only on Ubuntu/Debian with the needed dependencies.

Or if it is not desired to install npm (as the original author has mentioned), the following can be used after unzipping the binary file.

# Install the required packages listed in deb.deps for chrome
RUN while read pkg; do \
      apt-get satisfy -y --no-install-recommends "${pkg}"; \
    done < opt/chrome/deb.deps;

I found the mentioned solutions in the following pages:

https://developer.chrome.com/blog/chrome-for-testing/#how-can-i-get-chrome-for-testing-binaries https://pptr.dev/browsers-api https://github.com/GoogleChromeLabs/chrome-for-testing?tab=readme-ov-file#how-to-install-the-system-level-dependencies-required-for-archived-linux64-binaries

Hope this saves time for the next person with the same question :)

Comments

0

This is what I do using Deno

`deno -A fetch_chrome_for_testing.js`

// Fetch and unzip Chrome-For-Testing Canary Channel
// deno run -A unzip.js
/* eslint-disable no-console */
/* global Deno, Intl */

"use strict";

// import { parse } from "https://deno.land/std/flags/mod.ts";
import { exists } from "https://deno.land/std/fs/mod.ts";
import { basename, dirname } from "https://deno.land/std/path/mod.ts";
import {
  ERR_HTTP_RANGE,
  HttpReader,
  terminateWorkers,
  Uint8ArrayReader,
  Uint8ArrayWriter,
  ZipReader,
} from "https://raw.githubusercontent.com/gildas-lormeau/zip.js/master/index.js";

const executables = new Set([
  "chrome",
  "chrome-wrapper",
  "chrome_crashpad_handler",
  "chrome_sandbox",
  "libEGL.so",
  "libGLESv2.so",
  "libvk_swiftshader.so",
  "libvulkan.so.1",
  //"nacl_helper",
  //"nacl_helper_bootstrap",
  //"nacl_irt_x86_64.nexe",
  "xdg-mime",
  "xdg-settings",
]);

const json = await (await fetch(
  "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json",
)).json();
const {
  url,
} = json.channels.Canary.downloads.chrome.find(({
  platform,
}) => platform === "linux64");

console.log(`Fetch ${url}`);

const response = await fetch(url);

const length = response.headers.get("content-length");

const ab = new ArrayBuffer(length);

const uint8 = new Uint8Array(ab);

const encoder = new TextEncoder();

let offset = 0;

async function log(bytes) {
  // https://medium.com/deno-the-complete-reference/deno-nuggets-overwrite-a-console-log-line-2513e52e264b
  await Deno.stdout.write(
    encoder.encode(`${bytes} of ${length} bytes written.\r`),
  );
}
// Just so we see what's going on
await response.body.pipeTo(
  new WritableStream({
    start() {
      console.log("Start reading stream.");
    },
    async write(value) {
      uint8.set(value, offset);
      await log(offset += value.length);
    },
    close() {
      console.log("\nDone reading stream.");
    },
  }),
);

// const ab = await response.arrayBuffer();
// console.log(ab.byteLength);

await Deno.writeFile("chrome-linux64.zip", uint8);

unzip({ _: ["chrome-linux64.zip"] }).catch((error) =>
  console.error(error.toString())
);

async function unzip(args) {
  if (args.l) {
    await listEntries(args.l || args._[0]);
  } else {
    const archive = args._.shift();
    if (archive) {
      await unzipEntries(archive, args._);
      await Deno.remove("chrome-linux64.zip");
    }
  }
}

async function unzipEntries(archive, filenames) {
  const zipReader = new ZipReader(await getReader(archive));
  const entries = await zipReader.getEntries();
  let selectedEntries;
  if (filenames.length) {
    selectedEntries = entries.filter((entry) =>
      filenames.includes(entry.filename)
    );
  } else {
    selectedEntries = entries;
  }
  await Promise.all(selectedEntries.map(async (entry) => {
    const entryDirectory = dirname(entry.filename);
    if (!await exists(entryDirectory)) {
      await Deno.mkdir(entryDirectory, { recursive: true });
    }
    if (!entry.directory) {
      await Deno.writeFile(
        entry.filename,
        await entry.getData(new Uint8ArrayWriter()),
      );
    }
  }));
  await terminateWorkers();
  for (const file of executables) {
    await Deno.chmod(`chrome-linux64/${file}`, 0o764);
  }
}

async function listEntries(archive) {
  const zipReader = new ZipReader(await getReader(archive));
  const entries = await zipReader.getEntries();
  let totalSize = 0;
  console.log("Archive: ", archive);
  let maxNameLength = 0;
  const formattedData = entries.map((entry) => {
    const length = formatLength(entry.uncompressedSize);
    const splitDate = entry.lastModDate.toISOString().split("T");
    const date = splitDate[0].padStart(11);
    const time = splitDate[1].match(/([^:]+:[^:]+):/)[1].padEnd(7);
    const name = entry.filename;
    totalSize += entry.uncompressedSize;
    maxNameLength = Math.max(maxNameLength, length.length);
    return { length, date, time, name };
  });
  console.log(
    "Length".padStart(maxNameLength - 1, " "),
    "     Date    Time    Name",
  );
  const lengthSeparator = "-".padStart(maxNameLength, "-");
  console.log(lengthSeparator, " ---------- -----   ----");
  formattedData.forEach(({ length, date, time, name }) =>
    console.log(length.padStart(maxNameLength), date, time, name)
  );
  console.log(lengthSeparator, "                    ----");
  console.log(formatLength(totalSize));
}

function formatLength(length) {
  return new Intl.NumberFormat().format(length);
}

async function getReader(archive) {
  if (/^https?:/.test(archive)) {
    try {
      return new HttpReader(archive, {
        useRangeHeader: true,
        forceRangeRequests: true,
      });
    } catch (error) {
      if (error.message == ERR_HTTP_RANGE) {
        try {
          return new HttpReader(archive, { useRangeHeader: true });
        } catch (error) {
          if (error.message == ERR_HTTP_RANGE) {
            return new HttpReader(archive);
          } else {
            throw error;
          }
        }
      } else {
        throw error;
      }
    }
  } else {
    if (!basename(archive).includes(".")) {
      archive += ".zip";
    }
    return new Uint8ArrayReader(await Deno.readFile(archive));
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.