blob: 35c4992efb1f7305472a961ffaffc2fe8c114d2d [file] [log] [blame]
// This program serves a web application that displays screnshots captured by Puppeteer tests. It
// is meant to be used exclusively as a local development tool.
//
// Usage:
//
// $ bazel run --config=mayberemote //:puppeteer_screenshot_server
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/puppeteer-tests/bazel/extract_puppeteer_screenshots/extract"
"go.skia.org/infra/puppeteer-tests/bazel/puppeteer_screenshot_server/rpc_types"
)
func main() {
var port = flag.Int("port", 8000, "TCP port to serve the web interface.")
flag.Parse()
// Get the path to the repository root (and ensure we are running under Bazel).
workspaceDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
if workspaceDir == "" {
sklog.Fatal("The BUILD_WORKSPACE_DIRECTORY environment variable is not set. Are you running this program via Bazel?")
}
// Get working directory.
workDir, err := os.Getwd()
if err != nil {
sklog.Fatalf("Could not get the working directory: %s", err)
}
// Create temporary directory where we will extract Puppeteer screenshots.
screenshotsDir, err := os.MkdirTemp("/tmp", "puppeteer-screenshot-server-*")
if err != nil {
sklog.Fatalf("Could not create temporary directory: %s", err)
}
defer func() {
if err := os.RemoveAll(screenshotsDir); err != nil {
sklog.Fatalf("Cloud not delete temporary directory: %s", err)
}
}()
fmt.Printf("Screenshots will be extracted at: %s\n", screenshotsDir)
// Compute static assets dir (assumes the program is running under Bazel).
staticAssetsDir := filepath.Join(workDir, "puppeteer-tests", "pages", "development")
if _, err := os.Stat(staticAssetsDir); os.IsNotExist(err) {
sklog.Fatalf("Stating directory with static assets: %s", err)
}
// Serve the UI.
if err := serve(*port, workspaceDir, staticAssetsDir, screenshotsDir); err != nil {
sklog.Fatal("HTTP server error: %s", err)
}
}
func serve(port int, workspaceDir, staticAssetsDir, screenshotsDir string) error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join(staticAssetsDir, "index.html"))
})
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticAssetsDir))))
http.Handle("/screenshots/", http.StripPrefix("/screenshots/", http.FileServer(http.Dir(screenshotsDir))))
http.HandleFunc("/rpc/get-screenshots", func(w http.ResponseWriter, r *http.Request) {
handleGetScreenshotsRPC(w, r, workspaceDir, screenshotsDir)
})
hostname, err := os.Hostname()
if err != nil {
sklog.Fatalf("Could not resolve the hostname: %s", err)
}
fmt.Printf("Serving Puppeteer screenshots viewer at: http://%s:%d\n", hostname, port)
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
func handleGetScreenshotsRPC(w http.ResponseWriter, r *http.Request, workspaceDir, screenshotsDir string) {
// Extract screenshots.
if err := extract.Extract(workspaceDir, screenshotsDir); err != nil {
httputils.ReportError(w, err, "Could not extract screenshots.", http.StatusInternalServerError)
return
}
// Scan screenshots directory and build RPC response.
entries, err := os.ReadDir(screenshotsDir)
if err != nil {
httputils.ReportError(w, err, "Error scanning screenshots directory.", http.StatusInternalServerError)
return
}
response := rpc_types.GetScreenshotsRPCResponse{
ScreenshotsByApplication: map[string][]rpc_types.Screenshot{},
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".png") {
sklog.Infof("Ignoring non-PNG file in screenshots directory: %s", entry.Name())
continue
}
// Determine the application and test name for the current screenshot.
//
// If a screenshot does not follow the <app-name>_<test-name>.png naming pattern, we will give
// it a special application name, and use the entire file name (minus the extension) as the
// test name.
appAndTestName := strings.SplitN(strings.TrimSuffix(entry.Name(), ".png"), "_", 2)
app := "(unknown application)"
testName := appAndTestName[0]
if len(appAndTestName) == 2 {
app = appAndTestName[0]
testName = appAndTestName[1]
}
// Add screenshot to RPC response.
response.ScreenshotsByApplication[app] = append(response.ScreenshotsByApplication[app], rpc_types.Screenshot{
TestName: testName,
URL: fmt.Sprintf("/screenshots/%s", entry.Name()),
})
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
httputils.ReportError(w, err, "Failed to encode JSON response.", http.StatusInternalServerError)
return
}
}