#!/usr/bin/env bash echo "Running Ory Kratos E2E Tests..." echo "" set -euxo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/../.." make .bin/hydra export PATH=.bin:$PATH export KRATOS_PUBLIC_URL=http://localhost:4433/ export KRATOS_BROWSER_URL=http://localhost:4433/ export KRATOS_ADMIN_URL=http://localhost:4434/ export KRATOS_UI_URL=http://localhost:4456/ export KRATOS_UI_REACT_URL=http://localhost:4458/ export KRATOS_UI_REACT_NATIVE_URL=http://localhost:19006/ export LOG_LEAK_SENSITIVE_VALUES=true export DEV_DISABLE_API_FLOW_ENFORCEMENT=true export COOKIE_SECRET=kweifawskf23weas export CSRF_COOKIE_NAME=node_csrf_token export CSRF_COOKIE_SECRET=lkaw9oe8isedrhq2 base=$(pwd) setup=yes dev=no nokill=no cleanup=no for i in "$@"; do case $i in --no-kill) nokill=yes shift # past argument=value ;; --only-setup) setup=only shift # past argument=value ;; --no-setup) setup=no shift # past argument=value ;; --dev) dev=yes shift # past argument=value ;; --cleanup) cleanup=yes shift # past argument=value ;; esac done cleanup() { # Temporarily disable exit on error for cleanup (services may not be running) set +e echo "=== E2E Cleanup ===" # Step 1: Kill process supervisors FIRST (they respawn children) echo "Killing process supervisors (modd, nodemon)..." pkill -9 -f "modd" 2>/dev/null pkill -9 -f "nodemon" 2>/dev/null sleep 2 # Step 2: Kill Kratos and webhook (were managed by modd) echo "Killing Kratos and webhook..." pkill -9 -f "kratos serve" 2>/dev/null pkill -9 -f "\.bin/kratos" 2>/dev/null pkill -9 -f "\.bin/webhook" 2>/dev/null killall kratos 2>/dev/null killall webhook 2>/dev/null # Step 3: Kill Node.js services echo "Killing Node.js services..." pkill -9 -f "node.*proxy" 2>/dev/null pkill -9 -f "kratos-selfservice-ui-node" 2>/dev/null pkill -9 -f "kratos-selfservice-ui-react-nextjs" 2>/dev/null pkill -9 -f "expo" 2>/dev/null pkill -9 -f "webpack.*19006" 2>/dev/null # Step 4: Kill Hydra processes echo "Killing Hydra processes..." killall hydra 2>/dev/null killall hydra-login-consent 2>/dev/null killall hydra-kratos-login-consent 2>/dev/null pkill -9 -f "hydra serve" 2>/dev/null # Step 5: Kill any remaining processes on known ports echo "Cleaning up any remaining port listeners..." for port in 4433 4434 4444 4445 4446 4455 4456 4458 4744 4745 4746 19006; do pid=$(lsof -ti:$port 2>/dev/null) if [ -n "$pid" ]; then echo " Killing PID $pid on port $port" kill -9 $pid 2>/dev/null fi done # Step 6: Stop and remove Docker containers echo "Stopping Docker containers..." docker stop kratos_test_database_mysql kratos_test_database_postgres kratos_test_database_cockroach mailslurper kratos_test_hydra 2>/dev/null docker rm kratos_test_database_mysql kratos_test_database_postgres kratos_test_database_cockroach mailslurper kratos_test_hydra 2>/dev/null echo "=== Cleanup Complete ===" # Re-enable exit on error set -e } prepare() { echo "::group::prepare" if [[ "${nokill}" == "no" ]]; then cleanup fi if [ -z ${TEST_DATABASE_POSTGRESQL-} ]; then docker rm -f kratos_test_database_mysql kratos_test_database_postgres kratos_test_database_cockroach || true docker run --name kratos_test_database_mysql -p 3444:3306 -e MYSQL_ROOT_PASSWORD=secret -d mysql:8.0 docker run --name kratos_test_database_postgres -p 3445:5432 -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=postgres -d postgres:14 postgres -c log_statement=all docker run --name kratos_test_database_cockroach -p 3446:26257 -d cockroachdb/cockroach:latest-v25.4 start-single-node --insecure export TEST_DATABASE_MYSQL="mysql://root:secret@(localhost:3444)/mysql?parseTime=true&multiStatements=true" export TEST_DATABASE_POSTGRESQL="postgres://postgres:secret@localhost:3445/postgres?sslmode=disable" export TEST_DATABASE_COCKROACHDB="cockroach://root@localhost:3446/defaultdb?sslmode=disable" fi if [ -z "${NODE_UI_PATH-}" ]; then node_ui_dir="$(mktemp -d -t ci-XXXXXXXXXX)/kratos-selfservice-ui-node" git clone --depth 1 --branch master https://github.com/ory/kratos-selfservice-ui-node.git "$node_ui_dir" (cd "$node_ui_dir" && npm i --legacy-peer-deps && npm run build) else node_ui_dir="${NODE_UI_PATH}" fi if [ -z "${RN_UI_PATH-}" ]; then rn_ui_dir="$(mktemp -d -t ci-XXXXXXXXXX)/kratos-selfservice-ui-react-native" git clone --depth 1 --branch master https://github.com/ory/kratos-selfservice-ui-react-native.git "$rn_ui_dir" (cd "$rn_ui_dir" && npm i) else rn_ui_dir="${RN_UI_PATH}" fi if [ -z "${REACT_UI_PATH-}" ]; then react_ui_dir="$(mktemp -d -t ci-XXXXXXXXXX)/ory/kratos-selfservice-ui-react-nextjs" git clone --depth 1 --branch master https://github.com/ory/kratos-selfservice-ui-react-nextjs.git "$react_ui_dir" (cd "$react_ui_dir" && npm i) else react_ui_dir="${REACT_UI_PATH}" fi ( rm test/e2e/proxy.json || true echo '"express"' > test/e2e/proxy.json cd test/e2e/proxy npm i ) if [ -z ${CI-} ]; then docker rm mailslurper hydra hydra-ui -f || true docker run --name mailslurper -p 4436:4436 -p 4437:4437 -p 1025:1025 oryd/mailslurper:latest-smtps > "${base}/test/e2e/mailslurper.e2e.log" 2>&1 & fi # Check if any ports that we need are open already nc -zv localhost 4444 && exit 1 nc -zv localhost 4445 && exit 1 nc -zv localhost 4446 && exit 1 nc -zv localhost 4455 && exit 1 nc -zv localhost 19006 && exit 1 nc -zv localhost 4456 && exit 1 nc -zv localhost 4458 && exit 1 nc -zv localhost 4744 && exit 1 nc -zv localhost 4745 && exit 1 ( cd "$rn_ui_dir" KRATOS_URL=http://localhost:4433 CI=1 npm run web \ >"${base}/test/e2e/rn-profile-app.e2e.log" 2>&1 & ) hydra serve all -c test/e2e/hydra.yml --dev >"${base}/test/e2e/hydra.e2e.log" 2>&1 & (cd test/e2e; npm run wait-on -- -l -t 300000 http-get://localhost:4445/health/alive) hydra_client=$(hydra create oauth2-client \ --endpoint http://localhost:4445 \ --grant-type authorization_code --grant-type refresh_token \ --response-type code --response-type id_token \ --scope openid --scope offline \ --redirect-uri http://localhost:4455/self-service/methods/oidc/callback/hydra \ --format json) export OIDC_HYDRA_CLIENT_ID=$(jq -r '.client_id' <<< "$hydra_client" ) export OIDC_HYDRA_CLIENT_SECRET=$(jq -r '.client_secret' <<< "$hydra_client" ) google_client=$(hydra create oauth2-client \ --endpoint http://localhost:4445 \ --grant-type authorization_code --grant-type refresh_token \ --response-type code --response-type id_token \ --scope openid --scope offline \ --redirect-uri http://localhost:4455/self-service/methods/oidc/callback/google \ --format json) export OIDC_GOOGLE_CLIENT_ID=$(jq -r '.client_id' <<< "$google_client" ) export OIDC_GOOGLE_CLIENT_SECRET=$(jq -r '.client_secret' <<< "$google_client" ) github_client=$(hydra create oauth2-client \ --endpoint http://localhost:4445 \ --grant-type authorization_code --grant-type refresh_token \ --response-type code --response-type id_token \ --scope openid --scope offline \ --redirect-uri http://localhost:4455/self-service/methods/oidc/callback/github \ --format json) export OIDC_GITHUB_CLIENT_ID=$(jq -r '.client_id' <<< "$github_client" ) export OIDC_GITHUB_CLIENT_SECRET=$(jq -r '.client_secret' <<< "$github_client" ) ( cd test/e2e/hydra-login-consent go build . PORT=4446 HYDRA_ADMIN_URL=http://localhost:4445 ./hydra-login-consent >"${base}/test/e2e/hydra-ui.e2e.log" 2>&1 & ) # Spin up another Hydra instance with the express node app used as the login UI for kratos-hydra OIDC provider tests DSN=memory SERVE_PUBLIC_PORT=4744 \ SERVE_ADMIN_PORT=4745 \ URLS_SELF_ISSUER=http://localhost:4744 \ LOG_LEVEL=trace \ URLS_LOGIN=http://localhost:4455/login \ URLS_CONSENT=http://localhost:4746/consent \ SECRETS_SYSTEM="[\"1234567890123456789012345678901\"]" \ hydra serve all --dev >"${base}/test/e2e/hydra-kratos.e2e.log" 2>&1 & (cd test/e2e; npm run wait-on -- -l -t 300000 http-get://127.0.0.1:4745/health/alive) dummy_client=$(hydra create oauth2-client \ --endpoint http://localhost:4745 \ --token-endpoint-auth-method client_secret_basic \ --grant-type authorization_code --grant-type refresh_token \ --response-type code --response-type id_token \ --scope openid --scope offline --scope email --scope website \ --redirect-uri http://localhost:5555/callback \ --redirect-uri https://ory-network-httpbin-ijakee5waq-ez.a.run.app/anything \ --format json) export CYPRESS_OIDC_DUMMY_CLIENT_ID=$(jq -r '.client_id' <<< "$dummy_client" ) export CYPRESS_OIDC_DUMMY_CLIENT_SECRET=$(jq -r '.client_secret' <<< "$dummy_client" ) ( cd test/e2e/hydra-kratos-login-consent go build . PORT=4746 HYDRA_ADMIN_URL=http://localhost:4745 ./hydra-kratos-login-consent >"${base}/test/e2e/hydra-kratos-ui.e2e.log" 2>&1 & ) ( cd "$node_ui_dir" PORT=4456 SECURITY_MODE=cookie npm run start \ >"${base}/test/e2e/ui-node.e2e.log" 2>&1 & ) if [ -z "${REACT_UI_PATH-}" ]; then ( cd "$react_ui_dir" NEXT_PUBLIC_KRATOS_PUBLIC_URL=http://localhost:4433 npm run build NEXT_PUBLIC_KRATOS_PUBLIC_URL=http://localhost:4433 npm run start -- --hostname 127.0.0.1 --port 4458 \ >"${base}/test/e2e/react-iu.e2e.log" 2>&1 & ) else ( cd "$react_ui_dir" PORT=4458 NEXT_PUBLIC_KRATOS_PUBLIC_URL=http://localhost:4433 npm run dev \ >"${base}/test/e2e/react-iu.e2e.log" 2>&1 & ) fi ( cd test/e2e/proxy PORT=4455 npm run start \ >"${base}/test/e2e/proxy.e2e.log" 2>&1 & ) # Make the environment available to Playwright env | grep KRATOS_ > test/e2e/playwright/playwright.env env | grep TEST_DATABASE_ >> test/e2e/playwright/playwright.env env | grep OIDC_ >> test/e2e/playwright/playwright.env env | grep CYPRESS_ >> test/e2e/playwright/playwright.env echo LOG_LEAK_SENSITIVE_VALUES=true >> test/e2e/playwright/playwright.env echo DEV_DISABLE_API_FLOW_ENFORCEMENT=true >> test/e2e/playwright/playwright.env echo "::endgroup::" } run() { echo "::group::run-prep" killall modd || true killall kratos || true export DSN=${1} nc -zv localhost 4434 && (echo "Port 4434 unavailable, used by" ; lsof -i:4434 ; exit 1) nc -zv localhost 4433 && (echo "Port 4433 unavailable, used by" ; lsof -i:4433 ; exit 1) ls -la . for profile in code email mobile oidc recovery recovery-mfa verification mfa mfa-optional spa network passwordless passkey webhooks oidc-provider oidc-provider-mfa two-steps; do go tool yq ea '. as $item ireduce ({}; . * $item )' test/e2e/profiles/kratos.base.yml "test/e2e/profiles/${profile}/.kratos.yml" > test/e2e/kratos.${profile}.yml cat "test/e2e/kratos.${profile}.yml" | envsubst | sponge "test/e2e/kratos.${profile}.yml" done cp test/e2e/kratos.email.yml test/e2e/kratos.generated.yml (go tool modd -f test/e2e/modd.conf >"${base}/test/e2e/kratos.e2e.log" 2>&1 &) # Having to wait 10 minutes for cockroach to apply the migrations is ridiculous but sometimes it takes that long in CI npm run wait-on -- -l -t 10m http-get://127.0.0.1:4434/health/ready \ http-get://127.0.0.1:4444/.well-known/openid-configuration \ http-get://127.0.0.1:4455/health/ready \ http-get://127.0.0.1:4445/health/ready \ http-get://127.0.0.1:4446/ \ http-get://127.0.0.1:4456/health/alive \ http-get://127.0.0.1:19006/ \ http-get://127.0.0.1:4437/mail \ http-get://127.0.0.1:4458/ \ http-get://127.0.0.1:4459/health echo "::endgroup::" if [[ $dev == "yes" ]]; then (cd test/e2e; npm run test:watch --) else if [ -z "${CYPRESS_RECORD_KEY-}" ]; then (cd test/e2e; npx cypress run --browser chrome ${CYPRESS_OPTS-}) else (cd test/e2e; npx cypress run --browser chrome ${CYPRESS_OPTS-} --record --tag "${2}" ) fi fi } usage() { echo $"This script runs the e2e tests. To run the tests just pick a database name: $0 Supported databases are 'sqlite', 'mysql', 'postgres', 'cockroach': $0 sqlite $0 mysql $0 postgres $0 cockroach ... If you are using a database other than SQLite, you need to set an environment variable that points to it: export TEST_DATABASE_MYSQL=... export TEST_DATABASE_POSTGRESQL=... export TEST_DATABASE_COCKROACHDB=... $0 The Makefile has a helper for that which uses Docker to start the databases: make test-resetdb source script/test-envs.sh $0 To run e2e tests in dev mode (useful for writing them), run: $0 --dev To set up all the services without running the tests, use: $0 --only-setup To then run the tests without the set up steps, use: $0 --no-setup To prevent processes from being killed during set up phase, use: $0 --no-kill If you are making changes to the kratos-selfservice-ui-node project as well, point the 'NODE_UI_PATH' environment variable to the path where the kratos-selfservice-ui-node project is checked out: export NODE_UI_PATH=$HOME/workspace/kratos-selfservice-ui-node export RN_UI_PATH=$HOME/workspace/kratos-selfservice-ui-react-native export REACT_UI_PATH=$HOME/workspace/kratos-selfservice-ui-react-nextjs $0 ..." } if [[ "${cleanup}" == "yes" ]]; then cleanup exit 0 fi export TEST_DATABASE_SQLITE="sqlite:///$(mktemp -d -t ci-XXXXXXXXXX)/db.sqlite?_fk=true&_journal=WAL" export TEST_DATABASE_MEMORY="memory" case "${1:-default}" in sqlite) echo "Database set up at: $TEST_DATABASE_SQLITE" dsn="${TEST_DATABASE_SQLITE}" db="sqlite" ;; mysql) dsn="${TEST_DATABASE_MYSQL}" db="mysql" ;; postgres) dsn="${TEST_DATABASE_POSTGRESQL}" db="postgres" ;; cockroach) dsn="${TEST_DATABASE_COCKROACHDB}" db="cockroach" ;; *) usage if [[ "${setup}" == "only" ]]; then prepare exit 0 else exit 1 fi ;; esac if [[ "${setup}" == "yes" ]]; then prepare fi run "${dsn}" "${db}"