devtools/e2e: add setup for running e2e in CI

A setup is added using docker-compose, which allows us to run e2e tests
locally and in CI.

As of this CL, e2e/basic.test.ts can be run locally using
`./e2e/docker/run.sh e2e/basic.test.ts`.

Change-Id: Ief17c51ed7ab7de9cc8c535a7061f18d75763ecd
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/326649
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/doc/frontend.md b/doc/frontend.md
index 591d2b0..7b96971 100644
--- a/doc/frontend.md
+++ b/doc/frontend.md
@@ -55,39 +55,18 @@
 list of comma-separated strings each representing a path of a module to load
 into memory.
 
-### Testing
+### End-to-End (E2E) Tests
 
-In addition to tests inside internal/frontend and internal/testing/integration,
-pages on pkg.go.dev may have accessibility tree and image snapshot tests. These
-tests will create diffs for inspection on failure. Timeouts and diff thresholds
-are configurable for image snapshots if adjustments are needed to prevent test
-flakiness. See the
+In addition to tests written in Go inside internal/frontend and
+internal/testing/integration, pages on pkg.go.dev may have accessibility tree
+and image snapshot tests. These tests will create diffs for inspection on
+failure. Timeouts and diff thresholds are configurable for image snapshots if
+adjustments are needed to prevent test flakiness. See the
 [API](https://github.com/americanexpress/jest-image-snapshot#%EF%B8%8F-api) for
 jest image snapshots for more information.
 
-The e2e tests require that npm and docker are installed on your machine.
-
-First run headless chrome
-
-    docker run --rm -e "CONNECTION_TIMEOUT=-1" -p 3000:3000 browserless/chrome:1.46-chrome-stable
-
-Then run the tests
-
-    BASE_URL=https://pkg.go.dev npm run e2e
-
-#### Writing E2E Tests
-
-Tests are written in the Jest framework using Puppeteer to drive a headless
-instance of Chrome.
-
-Familiarize yourself with the
-[Page](https://pptr.dev/#?product=Puppeteer&version=v5.5.0&show=api-class-page)
-class from the Puppeteer documenation. You'll find methods on this class that
-let you to interact with the page.
-
-Most tests will follow a similar structure but for details on the Jest
-framework and the various hooks and assertions see the
-[API](https://jestjs.io/docs/en/api).
+These tests are in the [e2e/ directory](../e2e). For details, see
+[e2e/README.md](../e2e/README.md).
 
 ## Static Assets
 
diff --git a/e2e/README.md b/e2e/README.md
new file mode 100644
index 0000000..9d56185
--- /dev/null
+++ b/e2e/README.md
@@ -0,0 +1,50 @@
+# End-to-End (E2E) Tests
+
+This directory contains end-to-end tests for pages on pkg.go.dev.
+
+## Running E2E Tests
+
+In order to run the tests, run this command from the root of the repository:
+
+```
+$ ./e2e/docker/run.sh
+```
+
+`./e2e/docker/run.sh` sets up a series of docker containers that run a postgres
+database, frontend, and headless chrome, and runs the e2e tests using headless
+chrome.
+
+Alternatively, you can run the tests against a website that is already running.
+
+First run headless chrome:
+
+    docker run --rm -e "CONNECTION_TIMEOUT=-1" -p 3000:3000 browserless/chrome:1.46-chrome-stable
+
+Then run the tests from the root of pkgsite:
+
+    ./all.bash npx jest [files]
+
+`PKGSITE_URL` can https://pkg.go.dev, or http://localhost:8080 if you have a
+local instance for the frontend running.
+
+### Understanding Test Failures
+
+If the tests failure, diffs will be created that show the cause of the failure.
+Timeouts and diff thresholds are configurable for image snapshots if
+adjustments are needed to prevent test flakiness. See the
+[API](https://github.com/americanexpress/jest-image-snapshot#%EF%B8%8F-api) for
+jest image snapshots for more information.
+
+### Writing E2E Tests
+
+Tests are written in the Jest framework using Puppeteer to drive a headless
+instance of Chrome.
+
+Familiarize yourself with the
+[Page](https://pptr.dev/#?product=Puppeteer&version=v5.5.0&show=api-class-page)
+class from the Puppeteer documenation. You'll find methods on this class that
+let you to interact with the page.
+
+Most tests will follow a similar structure but for details on the Jest
+framework and the various hooks and assertions see the
+[API](https://jestjs.io/docs/en/api).
diff --git a/e2e/docker/Dockerfile.frontend b/e2e/docker/Dockerfile.frontend
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/e2e/docker/Dockerfile.frontend
diff --git a/e2e/docker/docker-compose.yaml b/e2e/docker/docker-compose.yaml
new file mode 100644
index 0000000..9918d71
--- /dev/null
+++ b/e2e/docker/docker-compose.yaml
@@ -0,0 +1,65 @@
+# Copyright 2021 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+version: '3'
+services:
+  chrome:
+    image: browserless/chrome:1.46-chrome-stable
+    depends_on:
+      - frontend
+    ports:
+      - 3000:3000
+    environment:
+      - CONNECTION_TIMEOUT=120000
+  frontend:
+    build:
+      context: ../../
+      dockerfile: e2e/docker/Dockerfile.frontend
+    command: ./frontend -host=0.0.0.0:8080
+    depends_on:
+      - migrate
+    environment:
+      - GO_DISCOVERY_DATABASE_USER=postgres
+      - GO_DISCOVERY_DATABASE_PASSWORD=postgres
+      - GO_DISCOVERY_DATABASE_HOST=db
+      - GO_DISCOVERY_DATABASE_NAME=discovery_e2e_test
+      - PORT=8080
+    image: pkgsite_frontend
+    ports:
+      - 8080:8080
+  migrate:
+    depends_on:
+      - wait_for_db
+    image: migrate/migrate:v4.14.1
+    volumes:
+      - ../../migrations:/pkgsite/migrations
+    command:
+      [
+        '-path',
+        '/pkgsite/migrations',
+        '-database',
+        'postgres://postgres:postgres@db:5432/discovery_e2e_test?sslmode=disable',
+        'up',
+      ]
+  # wait_for_db is used to delay migrations until the database is ready for connections.
+  wait_for_db:
+    image: ubuntu:14.04
+    depends_on:
+      - db
+    command: >
+      /bin/bash -c "
+        while ! nc -z db 5432;
+        do
+          echo sleeping;
+          sleep 1;
+        done;
+        echo connected!;
+      "
+  db:
+    image: postgres:11.12
+    environment:
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_USER=postgres
+      - POSTGRES_DB=discovery_e2e_test
+    ports:
+        - 5428:5432
diff --git a/e2e/docker/run.sh b/e2e/docker/run.sh
new file mode 100755
index 0000000..ddfc82c
--- /dev/null
+++ b/e2e/docker/run.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+# Copyright 2021 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+outfile="/tmp/e2e-chrome-$$.log"
+start_browser() {
+  # trap "kill 0" EXIT # kill the browser process on exit
+
+  docker-compose -f e2e/docker/docker-compose.yaml up -d chrome >& $outfile &
+  echo "Starting browser, output at $outfile"
+  sleep 30
+
+  # Wait for the browser to start up.
+  while ! curl -s http://localhost:3000 > /dev/null; do
+    sleep 1
+  done
+  echo "Browser is up."
+}
+
+main() {
+  start_browser
+
+  local files="e2e"
+  for arg in "$@"; do
+    if [[ $arg == e2e/* ]];then
+      files=""
+    fi
+  done
+
+  # Find the repo root.
+  script_dir=""
+  pkgsite_dir=""
+  if [[ "$OSTYPE" == "darwin"* ]]; then
+    # readlink doesn't work on Mac. Replace with greadlink.
+    script_dir=$(dirname "$(greadlink -f "$0")")
+    pkgsite_dir=$(greadlink -f "${script_dir}/../..")
+  else
+    script_dir=$(dirname "$(readlink -f "$0")")
+    pkgsite_dir=$(readlink -f "${script_dir}/../..")
+  fi
+
+  cd "${pkgsite_dir}"
+  GO_DISCOVERY_E2E_BASE_URL="http://frontend:8080"
+  (./devtools/ci/nodejs.sh npx jest $files $@)
+  echo "Done!"
+
+  echo "----- Contents of $outfile -----"
+  cat $outfile
+  docker-compose -f e2e/docker/docker-compose.yaml stop
+}
+
+main $@