From e71c53d4d0f373df8f701b94b55b55c787acca2b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 9 Nov 2023 13:26:26 +0200 Subject: [PATCH] chore(site): add remote playwright support and script (#10445) --- docs/contributing/frontend.md | 17 +++++++ scripts/remote_playwright.sh | 83 +++++++++++++++++++++++++++++++++++ site/e2e/playwright.config.ts | 16 +++++-- 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100755 scripts/remote_playwright.sh diff --git a/docs/contributing/frontend.md b/docs/contributing/frontend.md index b1e4858ce5..3a207dd5cd 100644 --- a/docs/contributing/frontend.md +++ b/docs/contributing/frontend.md @@ -217,6 +217,23 @@ to test if the page is being rendered correctly, you should consider using the > ℹ️ For scenarios where you need to be authenticated, you can use > `test.use({ storageState: getStatePath("authState") })`. +For ease of debugging, it's possible to run a Playwright test in headful mode +running a Playwright server on your local machine, and executing the test inside +your workspace. + +You can either run `scripts/remote_playwright.sh` from `coder/coder` on your +local machine, or execute the following command if you don't have the repo +available: + +```bash +bash <(curl -sSL https://raw.githubusercontent.com/coder/coder/main/scripts/remote_playwright.sh) [workspace] +``` + +The `scripts/remote_playwright.sh` script will start a Playwright server on your +local machine and forward the necessary ports to your workspace. At the end of +the script, you will land _inside_ your workspace with environment variables set +so you can simply execute the test (`pnpm run playwright:test`). + ### Integration Test user interactions like "Click in a button shows a dialog", "Submit the form diff --git a/scripts/remote_playwright.sh b/scripts/remote_playwright.sh new file mode 100755 index 0000000000..57f3a4d49f --- /dev/null +++ b/scripts/remote_playwright.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +workspace=${1:-} +coder_repo=${2:-.} +port=${3:-3000} + +if [[ -z "${workspace}" ]]; then + echo "Usage: $0 [workspace coder/coder dir] [e2e port]" + exit 1 +fi + +main() { + # Check the Playwright version from the workspace so we have a 1-to-1 match + # between the current branch and what we're going to run locally. This is + # necessary because Playwright uses their own protocol for communicating + # between the server and client, and the protocol changes between versions. + echo "Checking Playwright version from \"${workspace}\"..." + # shellcheck disable=SC2029 # This is intended to expand client-side. + playwright_version="$(ssh "coder.${workspace}" "cat '${coder_repo}'/site/pnpm-lock.yaml | grep '^ /@playwright/test@' | cut -d '@' -f 3 | tr -d ':'")" + + echo "Found Playwright version ${playwright_version}..." + + # Let's store it in cache because, why not, this is ephemeral. + dest=~/.cache/coder-remote-playwright + echo "Initializing Playwright server in ${dest}..." + mkdir -p "${dest}" + cd "${dest}" + echo '{"dependencies":{"@playwright/test":"'"${playwright_version}"'"}}' >package.json + cat <<-EOF >server.mjs + import { chromium } from "@playwright/test"; + + const server = await chromium.launchServer({ headless: false }); + console.log(server.wsEndpoint()); + EOF + + npm_cmd=npm + if command -v pnpm >/dev/null; then + npm_cmd=pnpm + fi + echo "Running \"${npm_cmd} install\" to ensure local and remote are up-to-date..." + "${npm_cmd}" install + + echo "Running \"${npm_cmd} exec playwright install\" for browser binaries..." + "${npm_cmd}" exec playwright install + + playwright_out="$(mktemp -t playwright_server_out.XXXXXX)" + + rm "${playwright_out}" + mkfifo "${playwright_out}" + exec 3<>"${playwright_out}" + + echo "Starting Playwright server..." + ${npm_cmd} exec node server.mjs 1>&3 & + playwright_pid=$! + + trap ' + kill -INT ${playwright_pid} + exec 3>&- + rm "${playwright_out}" + wait ${playwright_pid} + ' EXIT + + echo "Waiting for Playwright to start..." + read -r ws_endpoint <&3 + if [[ ${ws_endpoint} != ws://* ]]; then + echo "Playwright failed to start." + echo "${ws_endpoint}" + cat "${playwright_out}" + exit 1 + fi + echo "Playwright started at ${ws_endpoint}" + + ws_port=${ws_endpoint##*:} + ws_port=${ws_port%/*} + + echo + echo "Starting SSH tunnel, run test via \"pnpm run playwright:test\"..." + # shellcheck disable=SC2029 # This is intended to expand client-side. + ssh -t -R "${ws_port}:127.0.0.1:${ws_port}" -L "${port}:127.0.0.1:${port}" coder."${workspace}" "export CODER_E2E_PORT='${port}'; export CODER_E2E_WS_ENDPOINT='${ws_endpoint}'; [[ -d '${coder_repo}/site' ]] && cd '${coder_repo}/site'; exec \"\$(grep \"\${USER}\": /etc/passwd | cut -d: -f7)\" -i -l" +} + +main diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index dcd82a6dfd..2cf0b7a494 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -6,6 +6,8 @@ export const port = process.env.CODER_E2E_PORT ? Number(process.env.CODER_E2E_PORT) : defaultPort; +export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; + const coderMain = path.join(__dirname, "../../enterprise/cmd/coder"); export const STORAGE_STATE = path.join(__dirname, ".auth.json"); @@ -34,9 +36,17 @@ export default defineConfig({ use: { baseURL: `http://localhost:${port}`, video: "retain-on-failure", - launchOptions: { - args: ["--disable-webgl"], - }, + ...(wsEndpoint + ? { + connectOptions: { + wsEndpoint: wsEndpoint, + }, + } + : { + launchOptions: { + args: ["--disable-webgl"], + }, + }), }, webServer: { url: `http://localhost:${port}/api/v2/deployment/config`,