Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
FROM node:8.5.0-alpine

ENV \
PHANTOMJS_VERSION=2.1.1 \
CASPERJS_VERSION=1.1.4 \
SLIMERJS_VERSION=0.10.3 \
BACKSTOPJS_VERSION=3.0.25 \
# Workaround to fix phantomjs-prebuilt installation errors
# See https://github.com/Medium/phantomjs/issues/707
NPM_CONFIG_UNSAFE_PERM=true

# Base packages
RUN apk add --no-cache \
bash \
curl \
python \
# Use GNU grep to avoid compatibility issues (busybox grep uses -r vs -R)
grep

# Installing dependencies from archives - not only this allows us to control versions,
# but the resulting image size is 130MB+ less (!) compared to an npm install (440MB vs 575MB).
RUN \
mkdir -p /opt && \
# PhantomJS
echo "Downloading PhantomJS v${PHANTOMJS_VERSION}..." && \
curl -sL "https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-${PHANTOMJS_VERSION}-linux-x86_64.tar.bz2" | tar jx && \
mv phantomjs-${PHANTOMJS_VERSION}-linux-x86_64 /opt/phantomjs && \
ln -s /opt/phantomjs/bin/phantomjs /usr/bin/phantomjs && \
echo "Fixing PhantomJS on Alpine" && \
curl -sL "https://github.com/dustinblackman/phantomized/releases/download/${PHANTOMJS_VERSION}/dockerized-phantomjs.tar.gz" | tar zx -C /

RUN \
# CasperJS
echo "Downloading CasperJS v${CASPERJS_VERSION}..." && \
curl -sL "https://github.com/casperjs/casperjs/archive/${CASPERJS_VERSION}.tar.gz" | tar zx && \
mv casperjs-${CASPERJS_VERSION} /opt/casperjs && \
ln -s /opt/casperjs/bin/casperjs /usr/bin/casperjs

RUN \
# SlimerJS
echo "Downloading SlimerJS v${SLIMERJS_VERSION}..." && \
curl -sL -O "http://download.slimerjs.org/releases/${SLIMERJS_VERSION}/slimerjs-${SLIMERJS_VERSION}.zip" && \
unzip -q slimerjs-${SLIMERJS_VERSION}.zip && rm -f slimerjs-${SLIMERJS_VERSION}.zip && \
mv slimerjs-${SLIMERJS_VERSION} /opt/slimerjs && \
# Run slimer with xvfb
echo '#!/usr/bin/env bash\nxvfb-run /opt/slimerjs/slimerjs "$@"' > /opt/slimerjs/slimerjs.sh && \
chmod +x /opt/slimerjs/slimerjs.sh && \
ln -s /opt/slimerjs/slimerjs.sh /usr/bin/slimerjs

RUN \
# BackstopJS
echo "Installing BackstopJS v${BACKSTOPJS_VERSION}..." && \
npm install -g backstopjs@${BACKSTOPJS_VERSION}

ENV \
CHROMIUM_VERSION=61.0 \
FIREFOX_VERSION=52.3 \
CHROME_PATH=/usr/bin/chromium-browser

# Chrome (from edge)
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/main --repository http://dl-cdn.alpinelinux.org/alpine/edge/community \
"chromium>${CHROMIUM_VERSION}"

# Firefox (from edge)
RUN apk add --no-cache \
"firefox-esr>${FIREFOX_VERSION}"

# SlimerJS dependencies
RUN \
apk add --no-cache \
dbus \
xvfb

# xvfb wrapper
COPY xvfb-run /usr/bin/xvfb-run

WORKDIR /src

ENTRYPOINT ["backstop"]
82 changes: 82 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# BackstopJS Docker Image

A self-contained Docker image to run [BackstopJS](https://github.com/garris/BackstopJS) with no external dependencies.

[Visual Regression Testing with BackstopJS in a Docker container](https://blog.docksal.io/visual-regression-testing-with-backstopjs-in-a-docker-container-dfd1b9ae8582)

Features:

- [BackstopJS 3.x](https://github.com/garris/BackstopJS)
- [PhantomJS](http://phantomjs.org/)
- [SlimerJS](https://slimerjs.org/) (with Firefox ESR)
- [CasperJS](http://casperjs.org/)
- Chromium


## Versions

- `backstopjs/backstopjs` - BackstopJS v3 with Chrome Headless support


## Usage

Use this image as if you were using a binary.
Working directory is expected to be mounted at `/src` in the container.

```
$ docker run --rm -v $(pwd):/src backstopjs/backstopjs --version
BackstopJS v3.0.25
```

You can also add a shell alias (in `.bashrc`, `.zshrc`, etc.) for convenience.

```
alias backstop='docker run --rm -v $(pwd):/src backstopjs/backstopjs "$@"'
```

Restart your shell or open a new one, then

```
$ backstopjs --version
BackstopJS v3.0.25
```


## Sample test

```
docker run --rm -v $(pwd):/src backstopjs/backstopjs init
docker run --rm -v $(pwd):/src backstopjs/backstopjs reference
docker run --rm -v $(pwd):/src backstopjs/backstopjs test
```


## Browser engines

By default BackstopJS is using Headless Chrome to take screenshots.

You can also use PhantomJS or SlimerJS/Firefox by setting `"engine": "phantomjs"` or `"engine": "slimerjs"` respectively
in `backstop.json`.

Chrome, PhantomJS, SlimerJS and Firefox ESR (extended support release) are pre-installed in the container.


## Limitations

`backstop openReport` is not (yet) supported.

When running SlimerJS, the user you are running the container as must have a home directory in order for Slimer
to start properly. You can work around this by setting the `HOME` variable:

```
docker run --rm --user 1000 -e HOME=/tmp/home backstopjs/backstopjs test
```


## Debugging

The following command will start a bash session in the container.

```
docker run --rm -v $(pwd):/src -it --entrypoint=bash backstopjs/backstopjs
```
195 changes: 195 additions & 0 deletions docker/xvfb-run
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/bin/sh

# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $

# This script starts an instance of Xvfb, the "fake" X server, runs a command
# with that server available, and kills the X server when done. The return
# value of the command becomes the return value of this script, except in cases
# where this script encounters an error.
#
# If anyone is using this to build a Debian package, make sure the package
# Build-Depends on xvfb, xbase-clients, and xfonts-base.

set -e

PROGNAME=xvfb-run
SERVERNUM=99
AUTHFILE=
ERRORFILE=/dev/null
STARTWAIT=3
SCREEN_WIDTH=${SCREEN_WIDTH:-'1920'}
SCREEN_HEIGHT=${SCREEN_HEIGHT:-'1080'}
XVFBARGS="-screen 0, ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x24"
LISTENTCP="-nolisten tcp"
XAUTHPROTO=.

echo "Using screen size ${SCREEN_WIDTH}x${SCREEN_HEIGHT}"
# Query the terminal to establish a default number of columns to use for
# displaying messages to the user. This is used only as a fallback in the event
# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH while the
# script is running, and this cannot, only being calculated once.)
DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true
if ! expr "$DEFCOLUMNS" : "[[:digit:]]\+$" >/dev/null 2>&1; then
DEFCOLUMNS=80
fi

# Display a message, wrapping lines at the terminal width.
message () {
echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS}
}

# Display an error message.
error () {
message "error: $*" >&2
}

# Display a usage message.
usage () {
if [ -n "$*" ]; then
message "usage error: $*"
fi
cat <<EOF
Usage: $PROGNAME [OPTION ...] COMMAND
Run COMMAND (usually an X client) in a virtual X server environment.
Options:
-a --auto-servernum try to get a free server number, starting at
--server-num
-e FILE --error-file=FILE file used to store xauth errors and Xvfb
output (default: $ERRORFILE)
-f FILE --auth-file=FILE file used to store auth cookie
(default: ./.Xauthority)
-h --help display this usage message and exit
-n NUM --server-num=NUM server number to use (default: $SERVERNUM)
-l --listen-tcp enable TCP port listening in the X server
-p PROTO --xauth-protocol=PROTO X authority protocol name to use
(default: xauth command's default)
-s ARGS --server-args=ARGS arguments (other than server number and
"-nolisten tcp") to pass to the Xvfb server
(default: "$XVFBARGS")
-w DELAY --wait=DELAY delay in seconds to wait for Xvfb to start
before running COMMAND (default: $STARTWAIT)
EOF
}

# Find a free server number by looking at .X*-lock files in /tmp.
find_free_servernum() {
# Sadly, the "local" keyword is not POSIX. Leave the next line commented in
# the hope Debian Policy eventually changes to allow it in /bin/sh scripts
# anyway.
#local i

i=$SERVERNUM
while [ -f /tmp/.X$i-lock ]; do
i=$(($i + 1))
done
echo $i
}

# Clean up files
clean_up() {
if [ -e "$AUTHFILE" ]; then
XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >>"$ERRORFILE" 2>&1
fi
if [ -n "$XVFB_RUN_TMPDIR" ]; then
if ! rm -r "$XVFB_RUN_TMPDIR"; then
error "problem while cleaning up temporary directory"
exit 5
fi
fi
}

# Parse the command line.
ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
--long auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-protocol:,server-args:,wait: \
--name "$PROGNAME" -- "$@")
GETOPT_STATUS=$?

if [ $GETOPT_STATUS -ne 0 ]; then
error "internal error; getopt exited with status $GETOPT_STATUS"
exit 6
fi

eval set -- "$ARGS"

while :; do
case "$1" in
-a|--auto-servernum) SERVERNUM=$(find_free_servernum); AUTONUM="yes" ;;
-e|--error-file) ERRORFILE="$2"; shift ;;
-f|--auth-file) AUTHFILE="$2"; shift ;;
-h|--help) SHOWHELP="yes" ;;
-n|--server-num) SERVERNUM="$2"; shift ;;
-l|--listen-tcp) LISTENTCP="" ;;
-p|--xauth-protocol) XAUTHPROTO="$2"; shift ;;
-s|--server-args) XVFBARGS="$2"; shift ;;
-w|--wait) STARTWAIT="$2"; shift ;;
--) shift; break ;;
*) error "internal error; getopt permitted \"$1\" unexpectedly"
exit 6
;;
esac
shift
done

if [ "$SHOWHELP" ]; then
usage
exit 0
fi

if [ -z "$*" ]; then
usage "need a command to run" >&2
exit 2
fi

if ! which xauth >/dev/null; then
error "xauth command not found"
exit 3
fi

# tidy up after ourselves
trap clean_up EXIT

# If the user did not specify an X authorization file to use, set up a temporary
# directory to house one.
if [ -z "$AUTHFILE" ]; then
XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)"
# Create empty file to avoid xauth warning
AUTHFILE=$(touch "$XVFB_RUN_TMPDIR/Xauthority")
fi

# Start Xvfb.
MCOOKIE=$(mcookie)
tries=10
while [ $tries -gt 0 ]; do
tries=$(( $tries - 1 ))
XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
add :$SERVERNUM $XAUTHPROTO $MCOOKIE
EOF
XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 &
XVFBPID=$!

sleep "$STARTWAIT"
if kill -0 $XVFBPID 2>/dev/null; then
break
elif [ -n "$AUTONUM" ]; then
# The display is in use so try another one (if '-a' was specified).
SERVERNUM=$((SERVERNUM + 1))
SERVERNUM=$(find_free_servernum)
continue
fi
error "Xvfb failed to start" >&2
exit 1
done

# Start the command and save its exit status.
set +e
DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1
RETVAL=$?
set -e

# Kill Xvfb now that the command has exited.
kill $XVFBPID

# Return the executed command's exit status.
exit $RETVAL

# vim:set ai et sts=4 sw=4 tw=80: