mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Add end-to-end tests using playwright (#257)
Add tests with playwright for: - add peer modal on first access - add peer modal on empty peer list - test install buttons and instructions for Linux, Docker, macOS, Windows and Android - check default ACL The tests are using a modified version of the getting started scripts to run a local environment of management services and run the dashboard from the current version Todo: - run tests before create docker container - add more tests
This commit is contained in:
2
.github/workflows/build_and_push.yml
vendored
2
.github/workflows/build_and_push.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependecies
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build
|
||||
|
||||
42
.github/workflows/e2e-tests.yml
vendored
Normal file
42
.github/workflows/e2e-tests.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: run e2e tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
e2e_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: setup-node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: install playwright
|
||||
run: npx playwright install
|
||||
|
||||
- name: install playwright deps
|
||||
run: npx playwright install-deps
|
||||
|
||||
- name: create test environment
|
||||
run: bash ./e2e-tests/create-test-env.sh
|
||||
|
||||
- name: run e2e tests
|
||||
run: npx playwright test --workers 2
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: |
|
||||
playwright-report/
|
||||
test-results/
|
||||
retention-days: 3
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -29,3 +29,15 @@ src/auth_config.json
|
||||
src/.local-config*.json
|
||||
/public/OidcServiceWorker.js
|
||||
/public/OidcTrustedDomains.js
|
||||
/e2e-tests/node_modules/
|
||||
/e2e-tests/playwright-report/
|
||||
/e2e-tests/test-results/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
.env
|
||||
Caddyfile
|
||||
docker-compose.yml
|
||||
machinekey/
|
||||
management.json
|
||||
turnserver.conf
|
||||
zitadel.env
|
||||
|
||||
3
e2e-tests/clean-test-env.sh
Normal file
3
e2e-tests/clean-test-env.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
rm -f docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json
|
||||
697
e2e-tests/create-test-env.sh
Normal file
697
e2e-tests/create-test-env.sh
Normal file
@@ -0,0 +1,697 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
handle_request_command_status() {
|
||||
PARSED_RESPONSE=$1
|
||||
FUNCTION_NAME=$2
|
||||
RESPONSE=$3
|
||||
if [[ $PARSED_RESPONSE -ne 0 ]]; then
|
||||
echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
handle_zitadel_request_response() {
|
||||
PARSED_RESPONSE=$1
|
||||
FUNCTION_NAME=$2
|
||||
RESPONSE=$3
|
||||
if [[ $PARSED_RESPONSE == "null" ]]; then
|
||||
echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
}
|
||||
|
||||
check_docker_compose() {
|
||||
if command -v docker-compose &> /dev/null
|
||||
then
|
||||
echo "docker-compose"
|
||||
return
|
||||
fi
|
||||
if docker compose --help &> /dev/null
|
||||
then
|
||||
echo "docker compose"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "docker-compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_jq() {
|
||||
if ! command -v jq &> /dev/null
|
||||
then
|
||||
echo "jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
wait_crdb() {
|
||||
set +e
|
||||
while true; do
|
||||
if $DOCKER_COMPOSE_COMMAND exec -T crdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then
|
||||
break
|
||||
fi
|
||||
echo -n " ."
|
||||
sleep 5
|
||||
done
|
||||
echo " done"
|
||||
set -e
|
||||
}
|
||||
|
||||
init_crdb() {
|
||||
echo -e "\nInitializing Zitadel's CockroachDB\n\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d crdb
|
||||
echo ""
|
||||
# shellcheck disable=SC2028
|
||||
echo -n "Waiting cockroachDB to become ready "
|
||||
wait_crdb
|
||||
$DOCKER_COMPOSE_COMMAND exec -T crdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
|
||||
handle_request_command_status $? "init_crdb failed" ""
|
||||
}
|
||||
|
||||
get_main_ip_address() {
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
interface=$(route -n get default | grep 'interface:' | awk '{print $2}')
|
||||
ip_address=$(ifconfig "$interface" | grep 'inet ' | awk '{print $2}')
|
||||
else
|
||||
interface=$(ip route | grep default | awk '{print $5}' | head -n 1)
|
||||
ip_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
|
||||
fi
|
||||
|
||||
echo "$ip_address"
|
||||
}
|
||||
|
||||
wait_pat() {
|
||||
PAT_PATH=$1
|
||||
set +e
|
||||
while true; do
|
||||
if [[ -f "$PAT_PATH" ]]; then
|
||||
break
|
||||
fi
|
||||
echo -n " ."
|
||||
sleep 1
|
||||
done
|
||||
echo " done"
|
||||
set -e
|
||||
}
|
||||
|
||||
wait_api() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
set +e
|
||||
while true; do
|
||||
curl -s --fail -o /dev/null "$INSTANCE_URL/auth/v1/users/me" -H "Authorization: Bearer $PAT"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
break
|
||||
fi
|
||||
echo -n " ."
|
||||
sleep 1
|
||||
done
|
||||
echo " done"
|
||||
set -e
|
||||
}
|
||||
|
||||
create_new_project() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
PROJECT_NAME="NETBIRD"
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/management/v1/projects" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "'"$PROJECT_NAME"'"}'
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.id')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_project" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
create_new_application() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
APPLICATION_NAME=$3
|
||||
BASE_REDIRECT_URL1=$4
|
||||
BASE_REDIRECT_URL2=$5
|
||||
LOGOUT_URL=$6
|
||||
ZITADEL_DEV_MODE=$7
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "'"$APPLICATION_NAME"'",
|
||||
"redirectUris": [
|
||||
"'"$BASE_REDIRECT_URL1"'",
|
||||
"'"$BASE_REDIRECT_URL2"'"
|
||||
],
|
||||
"postLogoutRedirectUris": [
|
||||
"'"$LOGOUT_URL"'"
|
||||
],
|
||||
"RESPONSETypes": [
|
||||
"OIDC_RESPONSE_TYPE_CODE"
|
||||
],
|
||||
"grantTypes": [
|
||||
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
|
||||
"OIDC_GRANT_TYPE_REFRESH_TOKEN"
|
||||
],
|
||||
"appType": "OIDC_APP_TYPE_USER_AGENT",
|
||||
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
|
||||
"version": "OIDC_VERSION_1_0",
|
||||
"devMode": '"$ZITADEL_DEV_MODE"',
|
||||
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
|
||||
"accessTokenRoleAssertion": true,
|
||||
"skipNativeAppSuccessPage": true
|
||||
}'
|
||||
)
|
||||
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.clientId')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_application" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
create_service_user() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/management/v1/users/machine" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userName": "netbird-service-account",
|
||||
"name": "Netbird Service Account",
|
||||
"description": "Netbird Service Account for IDP management",
|
||||
"accessTokenType": "ACCESS_TOKEN_TYPE_JWT"
|
||||
}'
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "create_service_user" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
create_service_user_secret() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
USER_ID=$3
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X PUT "$INSTANCE_URL/management/v1/users/$USER_ID/secret" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
)
|
||||
SERVICE_USER_CLIENT_ID=$(echo "$RESPONSE" | jq -r '.clientId')
|
||||
handle_zitadel_request_response "$SERVICE_USER_CLIENT_ID" "create_service_user_secret_id" "$RESPONSE"
|
||||
SERVICE_USER_CLIENT_SECRET=$(echo "$RESPONSE" | jq -r '.clientSecret')
|
||||
handle_zitadel_request_response "$SERVICE_USER_CLIENT_SECRET" "create_service_user_secret" "$RESPONSE"
|
||||
}
|
||||
|
||||
add_organization_user_manager() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
USER_ID=$3
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/management/v1/orgs/me/members" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "'"$USER_ID"'",
|
||||
"roles": [
|
||||
"ORG_USER_MANAGER"
|
||||
]
|
||||
}'
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "add_organization_user_manager" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
create_admin_user() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
USERNAME=$3
|
||||
PASSWORD=$4
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/management/v1/users/human/_import" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userName": "'"$USERNAME"'",
|
||||
"profile": {
|
||||
"firstName": "Zitadel",
|
||||
"lastName": "Admin"
|
||||
},
|
||||
"email": {
|
||||
"email": "'"$USERNAME"'",
|
||||
"isEmailVerified": true
|
||||
},
|
||||
"password": "'"$PASSWORD"'",
|
||||
"passwordChangeRequired": false
|
||||
}'
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "create_admin_user" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
add_instance_admin() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
USER_ID=$3
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X POST "$INSTANCE_URL/admin/v1/members" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "'"$USER_ID"'",
|
||||
"roles": [
|
||||
"IAM_OWNER"
|
||||
]
|
||||
}'
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "add_instance_admin" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
delete_auto_service_user() {
|
||||
INSTANCE_URL=$1
|
||||
PAT=$2
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X GET "$INSTANCE_URL/auth/v1/users/me" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
)
|
||||
USER_ID=$(echo "$RESPONSE" | jq -r '.user.id')
|
||||
handle_zitadel_request_response "$USER_ID" "delete_auto_service_user_get_user" "$RESPONSE"
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X DELETE "$INSTANCE_URL/admin/v1/members/$USER_ID" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_instance_permissions" "$RESPONSE"
|
||||
|
||||
RESPONSE=$(
|
||||
curl -sS -X DELETE "$INSTANCE_URL/management/v1/orgs/me/members/$USER_ID" \
|
||||
-H "Authorization: Bearer $PAT" \
|
||||
-H "Content-Type: application/json" \
|
||||
)
|
||||
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
|
||||
handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_org_permissions" "$RESPONSE"
|
||||
echo "$PARSED_RESPONSE"
|
||||
}
|
||||
|
||||
init_zitadel() {
|
||||
echo -e "\nInitializing Zitadel with NetBird's applications\n"
|
||||
INSTANCE_URL="$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||
|
||||
TOKEN_PATH=./machinekey/zitadel-admin-sa.token
|
||||
|
||||
echo -n "Waiting for Zitadel's PAT to be created "
|
||||
wait_pat "$TOKEN_PATH"
|
||||
echo "Reading Zitadel PAT"
|
||||
PAT=$(cat $TOKEN_PATH)
|
||||
if [ "$PAT" = "null" ]; then
|
||||
echo "Failed requesting getting Zitadel PAT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Waiting for Zitadel to become ready "
|
||||
wait_api "$INSTANCE_URL" "$PAT"
|
||||
|
||||
# create the zitadel project
|
||||
echo "Creating new zitadel project"
|
||||
PROJECT_ID=$(create_new_project "$INSTANCE_URL" "$PAT")
|
||||
|
||||
ZITADEL_DEV_MODE=false
|
||||
BASE_REDIRECT_URL=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN
|
||||
if [[ $NETBIRD_HTTP_PROTOCOL == "http" ]]; then
|
||||
ZITADEL_DEV_MODE=true
|
||||
fi
|
||||
|
||||
# create zitadel spa applications
|
||||
echo "Creating new Zitadel SPA Dashboard application"
|
||||
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "http://localhost:3000/nb-auth" "http://localhost:3000/nb-silent-auth" "http://localhost:3000/" "true")
|
||||
|
||||
echo "Creating new Zitadel SPA Cli application"
|
||||
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true")
|
||||
|
||||
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
|
||||
|
||||
SERVICE_USER_CLIENT_ID="null"
|
||||
SERVICE_USER_CLIENT_SECRET="null"
|
||||
|
||||
create_service_user_secret "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID"
|
||||
|
||||
DATE=$(add_organization_user_manager "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID")
|
||||
|
||||
ZITADEL_ADMIN_USERNAME="admin@localhost"
|
||||
ZITADEL_ADMIN_PASSWORD="testMe123@"
|
||||
|
||||
HUMAN_USER_ID=$(create_admin_user "$INSTANCE_URL" "$PAT" "$ZITADEL_ADMIN_USERNAME" "$ZITADEL_ADMIN_PASSWORD")
|
||||
|
||||
DATE="null"
|
||||
|
||||
DATE=$(add_instance_admin "$INSTANCE_URL" "$PAT" "$HUMAN_USER_ID")
|
||||
|
||||
DATE="null"
|
||||
DATE=$(delete_auto_service_user "$INSTANCE_URL" "$PAT")
|
||||
if [ "$DATE" = "null" ]; then
|
||||
echo "Failed deleting auto service user"
|
||||
echo "Please remove it manually"
|
||||
fi
|
||||
|
||||
export NETBIRD_AUTH_CLIENT_ID=$DASHBOARD_APPLICATION_CLIENT_ID
|
||||
export NETBIRD_AUTH_CLIENT_ID_CLI=$CLI_APPLICATION_CLIENT_ID
|
||||
export NETBIRD_IDP_MGMT_CLIENT_ID=$SERVICE_USER_CLIENT_ID
|
||||
export NETBIRD_IDP_MGMT_CLIENT_SECRET=$SERVICE_USER_CLIENT_SECRET
|
||||
export ZITADEL_ADMIN_USERNAME
|
||||
export ZITADEL_ADMIN_PASSWORD
|
||||
}
|
||||
|
||||
check_nb_domain() {
|
||||
DOMAIN=$1
|
||||
if [ "$DOMAIN-x" == "-x" ]; then
|
||||
echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$DOMAIN" == "netbird.example.com" ]; then
|
||||
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
|
||||
retrun 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
read_nb_domain() {
|
||||
READ_NETBIRD_DOMAIN=""
|
||||
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
|
||||
read -r READ_NETBIRD_DOMAIN < /dev/tty
|
||||
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
|
||||
read_nb_domain
|
||||
fi
|
||||
echo "$READ_NETBIRD_DOMAIN"
|
||||
}
|
||||
|
||||
initEnvironment() {
|
||||
CADDY_SECURE_DOMAIN=""
|
||||
ZITADEL_EXTERNALSECURE="false"
|
||||
ZITADEL_TLS_MODE="disabled"
|
||||
ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)"
|
||||
NETBIRD_PORT=80
|
||||
NETBIRD_HTTP_PROTOCOL="http"
|
||||
TURN_USER="self"
|
||||
TURN_PASSWORD=$(openssl rand -base64 32 | sed 's/=//g')
|
||||
TURN_MIN_PORT=49152
|
||||
TURN_MAX_PORT=65535
|
||||
|
||||
NETBIRD_DOMAIN=$(get_main_ip_address)
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -v+30M "+%Y-%m-%dT%H:%M:%SZ")
|
||||
else
|
||||
ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -d "+30 minutes" "+%Y-%m-%dT%H:%M:%SZ")
|
||||
fi
|
||||
|
||||
check_jq
|
||||
|
||||
DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
|
||||
|
||||
if [ -f zitadel.env ]; then
|
||||
echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
|
||||
echo "You can use the following commands:"
|
||||
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
|
||||
echo " rm -f docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json"
|
||||
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo Rendering initial files...
|
||||
renderDockerCompose > docker-compose.yml
|
||||
renderCaddyfile > Caddyfile
|
||||
renderZitadelEnv > zitadel.env
|
||||
echo "" > turnserver.conf
|
||||
echo "" > management.json
|
||||
|
||||
mkdir -p machinekey
|
||||
chmod 777 machinekey
|
||||
|
||||
init_crdb
|
||||
|
||||
echo -e "\nStarting Zidatel IDP for user management\n\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d caddy zitadel
|
||||
init_zitadel
|
||||
|
||||
echo -e "\nRendering NetBird files...\n"
|
||||
renderTurnServerConf > turnserver.conf
|
||||
renderManagementJson > management.json
|
||||
renderDashboardEnv > src/.local-config.json
|
||||
|
||||
echo -e "\nStarting NetBird services\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d
|
||||
echo -e "\nDone!\n"
|
||||
echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||
echo "Login with the following credentials:"
|
||||
echo "Username: $ZITADEL_ADMIN_USERNAME" | tee .env
|
||||
echo "Password: $ZITADEL_ADMIN_PASSWORD" | tee -a .env
|
||||
}
|
||||
|
||||
renderCaddyfile() {
|
||||
cat <<EOF
|
||||
{
|
||||
debug
|
||||
servers :80,:443 {
|
||||
protocols h1 h2c
|
||||
}
|
||||
}
|
||||
|
||||
:80${CADDY_SECURE_DOMAIN} {
|
||||
# Signal
|
||||
reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000
|
||||
# Management
|
||||
reverse_proxy /api/* management:80
|
||||
reverse_proxy /management.ManagementService/* h2c://management:80
|
||||
# Zitadel
|
||||
reverse_proxy /zitadel.admin.v1.AdminService/* h2c://zitadel:8080
|
||||
reverse_proxy /admin/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /zitadel.auth.v1.AuthService/* h2c://zitadel:8080
|
||||
reverse_proxy /auth/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /zitadel.management.v1.ManagementService/* h2c://zitadel:8080
|
||||
reverse_proxy /management/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /zitadel.system.v1.SystemService/* h2c://zitadel:8080
|
||||
reverse_proxy /system/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /assets/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /ui/* h2c://zitadel:8080
|
||||
reverse_proxy /oidc/v1/* h2c://zitadel:8080
|
||||
reverse_proxy /saml/v2/* h2c://zitadel:8080
|
||||
reverse_proxy /oauth/v2/* h2c://zitadel:8080
|
||||
reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080
|
||||
reverse_proxy /openapi/* h2c://zitadel:8080
|
||||
reverse_proxy /debug/* h2c://zitadel:8080
|
||||
# Dashboard
|
||||
reverse_proxy /* dashboard:80
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
renderTurnServerConf() {
|
||||
cat <<EOF
|
||||
listening-port=3478
|
||||
tls-listening-port=5349
|
||||
min-port=$TURN_MIN_PORT
|
||||
max-port=$TURN_MAX_PORT
|
||||
fingerprint
|
||||
lt-cred-mech
|
||||
user=$TURN_USER:$TURN_PASSWORD
|
||||
realm=wiretrustee.com
|
||||
cert=/etc/coturn/certs/cert.pem
|
||||
pkey=/etc/coturn/private/privkey.pem
|
||||
log-file=stdout
|
||||
no-software-attribute
|
||||
pidfile="/var/tmp/turnserver.pid"
|
||||
no-cli
|
||||
EOF
|
||||
}
|
||||
|
||||
renderManagementJson() {
|
||||
cat <<EOF
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:$NETBIRD_DOMAIN:3478"
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:$NETBIRD_DOMAIN:3478",
|
||||
"Username": "$TURN_USER",
|
||||
"Password": "$TURN_PASSWORD"
|
||||
}
|
||||
],
|
||||
"TimeBasedCredentials": false
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "$NETBIRD_HTTP_PROTOCOL",
|
||||
"URI": "$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||
},
|
||||
"HttpConfig": {
|
||||
"AuthIssuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN",
|
||||
"AuthAudience": "$NETBIRD_AUTH_CLIENT_ID",
|
||||
"OIDCConfigEndpoint":"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/.well-known/openid-configuration"
|
||||
},
|
||||
"IdpManagerConfig": {
|
||||
"ManagerType": "zitadel",
|
||||
"ClientConfig": {
|
||||
"Issuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
|
||||
"TokenEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/oauth/v2/token",
|
||||
"ClientID": "$NETBIRD_IDP_MGMT_CLIENT_ID",
|
||||
"ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET",
|
||||
"GrantType": "client_credentials"
|
||||
},
|
||||
"ExtraConfig": {
|
||||
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/management/v1"
|
||||
}
|
||||
},
|
||||
"PKCEAuthorizationFlow": {
|
||||
"ProviderConfig": {
|
||||
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"Scope": "openid profile email offline_access",
|
||||
"RedirectURLs": ["http://localhost:53000/","http://localhost:54000/"]
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
renderDashboardEnv() {
|
||||
cat <<EOF
|
||||
{
|
||||
"auth0Auth": "false",
|
||||
"authAuthority": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
|
||||
"authClientId": "$NETBIRD_AUTH_CLIENT_ID",
|
||||
"authScopesSupported": "openid profile email offline_access",
|
||||
"authAudience": "$NETBIRD_AUTH_CLIENT_ID",
|
||||
"apiOrigin": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
|
||||
"grpcApiOrigin": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
|
||||
"redirectURI": "/nb-auth",
|
||||
"silentRedirectURI": "/nb-silent-auth"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
renderZitadelEnv() {
|
||||
cat <<EOF
|
||||
ZITADEL_LOG_LEVEL=debug
|
||||
ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY
|
||||
ZITADEL_DATABASE_COCKROACH_HOST=crdb
|
||||
ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/crdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/crdb-certs/client.zitadel_user.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/crdb-certs/client.zitadel_user.key"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/crdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/crdb-certs/client.root.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/crdb-certs/client.root.key"
|
||||
ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE
|
||||
ZITADEL_TLS_ENABLED="false"
|
||||
ZITADEL_EXTERNALPORT=$NETBIRD_PORT
|
||||
ZITADEL_EXTERNALDOMAIN=$NETBIRD_DOMAIN
|
||||
ZITADEL_FIRSTINSTANCE_PATPATH=/machinekey/zitadel-admin-sa.token
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_SCOPES=openid
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE=$ZIDATE_TOKEN_EXPIRATION_DATE
|
||||
EOF
|
||||
}
|
||||
|
||||
renderDockerCompose() {
|
||||
cat <<EOF
|
||||
version: "3.4"
|
||||
services:
|
||||
# Caddy reverse proxy
|
||||
caddy:
|
||||
image: caddy
|
||||
restart: unless-stopped
|
||||
networks: [ netbird ]
|
||||
ports:
|
||||
- '443:443'
|
||||
- '80:80'
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- netbird_caddy_data:/data
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
# Management
|
||||
management:
|
||||
image: netbirdio/management:latest
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
volumes:
|
||||
- netbird_management:/var/lib/netbird
|
||||
- ./management.json:/etc/netbird/management.json
|
||||
command: [
|
||||
"--port", "80",
|
||||
"--log-file", "console",
|
||||
"--log-level", "info",
|
||||
"--disable-anonymous-metrics=false",
|
||||
"--single-account-mode-domain=netbird.selfhosted",
|
||||
"--dns-domain=netbird.selfhosted",
|
||||
"--idp-sign-key-refresh-enabled",
|
||||
]
|
||||
# Zitadel - identity provider
|
||||
zitadel:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'ghcr.io/zitadel/zitadel:v2.31.3'
|
||||
command: 'start-from-init --masterkeyFromEnv --tlsMode $ZITADEL_TLS_MODE'
|
||||
env_file:
|
||||
- ./zitadel.env
|
||||
depends_on:
|
||||
crdb:
|
||||
condition: 'service_healthy'
|
||||
volumes:
|
||||
- ./machinekey:/machinekey
|
||||
- netbird_zitadel_certs:/crdb-certs:ro
|
||||
# CockroachDB for zitadel
|
||||
crdb:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'cockroachdb/cockroach:v22.2.2'
|
||||
command: 'start-single-node --advertise-addr crdb'
|
||||
volumes:
|
||||
- netbird_crdb_data:/cockroach/cockroach-data
|
||||
- netbird_crdb_certs:/cockroach/certs
|
||||
- netbird_zitadel_certs:/zitadel-certs
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
|
||||
volumes:
|
||||
netbird_management:
|
||||
netbird_caddy_data:
|
||||
netbird_crdb_data:
|
||||
netbird_crdb_certs:
|
||||
netbird_zitadel_certs:
|
||||
|
||||
networks:
|
||||
netbird:
|
||||
EOF
|
||||
}
|
||||
|
||||
initEnvironment
|
||||
111
e2e-tests/pages/modals/add-peer-modal.ts
Normal file
111
e2e-tests/pages/modals/add-peer-modal.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Page, test, expect } from "@playwright/test";
|
||||
|
||||
export class AddPeerModal {
|
||||
private readonly addPeerModal = this.page.getByTestId('add-peer-modal').locator('div').nth(2)
|
||||
private readonly linuxTab = this.page.getByTestId('add-peer-modal-linux-tab')
|
||||
private readonly windowsTab = this.page.getByTestId('add-peer-modal-windows-tab')
|
||||
private readonly macTab = this.page.getByTestId('add-peer-modal-mac-tab')
|
||||
private readonly androidTab = this.page.getByTestId('add-peer-modal-android-tab')
|
||||
private readonly dockerTab = this.page.getByTestId('add-peer-modal-docker-tab')
|
||||
private readonly linuxTabText = this.page.locator('pre').filter({ hasText: 'curl -fsSL https://pkgs.netbird.io/install.sh | sh' })
|
||||
private readonly windowsDownloadButton = this.page.getByTestId('download-windows-button')
|
||||
private readonly intelDownloadButton = this.page.getByTestId('download-intel-button')
|
||||
private readonly m1M2DownloadButton = this.page.getByTestId('download-m1-m2-button')
|
||||
private readonly androidDownloadButton = this.page.getByTestId('download-android-button')
|
||||
private readonly dockerDownloadButton = this.page.getByTestId('download-docker-button')
|
||||
private readonly closeButton = this.page.getByLabel('Close', { exact: true })
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async assertPeerModalIsVisible() {
|
||||
await test.step('Assert that add peer modal is visible', async () => {
|
||||
await expect(this.addPeerModal).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async assertPeerModalIsNotVisible() {
|
||||
await test.step('Assert that add peer modal is not visible', async () => {
|
||||
await expect(this.addPeerModal).not.toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async openLinuxTab() {
|
||||
await test.step('Open Linux tab on add peer modal', async () => {
|
||||
await this.linuxTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openWindowsTab() {
|
||||
await test.step('Open Windows tab on add peer modal', async () => {
|
||||
await this.windowsTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openMacTab() {
|
||||
await test.step('Open MacOS tab on add peer modal', async () => {
|
||||
await this.macTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openAndroidTab() {
|
||||
await test.step('Open Android tab on add peer modal', async () => {
|
||||
await this.androidTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openDockerTab() {
|
||||
await test.step('Open Docker tab on add peer modal', async () => {
|
||||
await this.dockerTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async assertLinuxTabHasCorrectText() {
|
||||
await test.step('Assert Linux tab has correct installation text', async () => {
|
||||
await expect(this.linuxTabText).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async assertWindowsDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Windows download button has a correct link', async () => {
|
||||
await expect(this.windowsDownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/windows/x64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertIntelDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Intel download button has a correct link', async () => {
|
||||
await expect(this.intelDownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/macos/amd64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertM1M2DownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert M1 & M2 download button has a correct link', async () => {
|
||||
await expect(this.m1M2DownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/macos/arm64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertAndroidDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Android download button has a correct link', async () => {
|
||||
await expect(this.androidDownloadButton).toHaveAttribute('href', 'https://play.google.com/store/apps/details?id=io.netbird.client');
|
||||
})
|
||||
}
|
||||
|
||||
async assertDockerDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Docker download button has a correct link', async () => {
|
||||
await expect(this.dockerDownloadButton).toHaveAttribute('href', 'https://docs.docker.com/engine/install/');
|
||||
})
|
||||
}
|
||||
|
||||
async closeAddPeerModal() {
|
||||
await test.step('Close Add peer modal', async () => {
|
||||
await this.closeButton.click();
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AddPeerModal;
|
||||
15
e2e-tests/pages/peers-page.ts
Normal file
15
e2e-tests/pages/peers-page.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Page, test, expect } from "@playwright/test";
|
||||
|
||||
export class PeersPage {
|
||||
private readonly addNewPeerButton = this.page.getByTestId('add-new-peer-button')
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async clickOnAddNewPeerButton() {
|
||||
await test.step('Click on Add new peer Button to open Add peer modal', async () => {
|
||||
await this.addNewPeerButton.click();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default PeersPage;
|
||||
15
e2e-tests/pages/top-menu.ts
Normal file
15
e2e-tests/pages/top-menu.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Page, test} from "@playwright/test";
|
||||
|
||||
export class TopMenu {
|
||||
private readonly accessControlButton = this.page.getByTestId('access-control-page')
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async clickOnAccessControlOnTopMenu() {
|
||||
await test.step('Click on Access Control page on a top menu', async () => {
|
||||
await this.accessControlButton.click();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default TopMenu;
|
||||
33
e2e-tests/tests/access-control.test.ts
Normal file
33
e2e-tests/tests/access-control.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {AddPeerModal} from '../pages/modals/add-peer-modal'
|
||||
import {TopMenu} from '../pages/top-menu';
|
||||
|
||||
const URL = 'https://app.netbird.io/'
|
||||
const localUrl = 'http://localhost:3000/'
|
||||
let addPeerModal: AddPeerModal;
|
||||
let topMenu: TopMenu;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
addPeerModal = new AddPeerModal(page);
|
||||
await page.goto(localUrl);
|
||||
await page.getByPlaceholder('username@domain').fill('admin@localhost');
|
||||
await page.getByRole('button', { name: 'next' }).click();
|
||||
await page.getByLabel('Password').fill('testMe123@');
|
||||
await page.getByRole('button', { name: 'next' }).click();
|
||||
const skipButton = page.getByRole('button', { name: 'skip' });
|
||||
if (await skipButton.isVisible({ timeout: 300 })) {
|
||||
await skipButton.click();
|
||||
}
|
||||
await addPeerModal.assertPeerModalIsVisible();
|
||||
});
|
||||
|
||||
test('Confirm that new user has Default access', async ({ page }) => {
|
||||
topMenu = new TopMenu(page);
|
||||
await addPeerModal.closeAddPeerModal();
|
||||
await addPeerModal.assertPeerModalIsNotVisible();
|
||||
await topMenu.clickOnAccessControlOnTopMenu();
|
||||
await expect(page.getByRole('cell', { name: 'Default' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
await expect(page.getByTestId('confirm-delete-modal-title')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
56
e2e-tests/tests/peers.test.ts
Normal file
56
e2e-tests/tests/peers.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { test } from '@playwright/test';
|
||||
import {AddPeerModal} from '../pages/modals/add-peer-modal'
|
||||
import {PeersPage} from '../pages/peers-page'
|
||||
|
||||
const URL = 'https://app.netbird.io/'
|
||||
const localUrl = 'http://localhost:3000/'
|
||||
let addPeerModal: AddPeerModal;
|
||||
let peersPage: PeersPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
addPeerModal = new AddPeerModal(page);
|
||||
await page.goto(localUrl);
|
||||
await page.getByPlaceholder('username@domain').fill('admin@localhost');
|
||||
await page.getByRole('button', { name: 'next' }).click();
|
||||
await page.getByLabel('Password').fill('testMe123@');
|
||||
await page.getByRole('button', { name: 'next' }).click();
|
||||
const skipButton = page.getByRole('button', { name: 'skip' });
|
||||
if (await skipButton.isVisible({ timeout: 300 })) {
|
||||
await skipButton.click();
|
||||
}
|
||||
await addPeerModal.assertPeerModalIsVisible();
|
||||
});
|
||||
|
||||
test('Test Linux tab on a first access add peer modal / @bc', async function ({ }) {
|
||||
await addPeerModal.openLinuxTab();
|
||||
await addPeerModal.assertLinuxTabHasCorrectText();
|
||||
});
|
||||
|
||||
test('Test Windows tab on a first access add peer modal / @bc', async ({ }) => {
|
||||
await addPeerModal.openWindowsTab();
|
||||
await addPeerModal.assertWindowsDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Test MacOS tab on a first access add peer modal / @bc', async ({ }) => {
|
||||
await addPeerModal.openMacTab();
|
||||
await addPeerModal.assertIntelDownloadButtonHasCorrectLink();
|
||||
await addPeerModal.assertM1M2DownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Test Android tab on a first access add peer modal', async ({ }) => {
|
||||
await addPeerModal.openAndroidTab();
|
||||
await addPeerModal.assertAndroidDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Test Docker tab on a first access add peer modal', async ({ }) => {
|
||||
await addPeerModal.openDockerTab();
|
||||
await addPeerModal.assertDockerDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Close and open Add peer modal', async ({ page }) => {
|
||||
peersPage = new PeersPage(page);
|
||||
await addPeerModal.closeAddPeerModal();
|
||||
await addPeerModal.assertPeerModalIsNotVisible();
|
||||
await peersPage.clickOnAddNewPeerButton();
|
||||
await addPeerModal.assertPeerModalIsVisible();
|
||||
});
|
||||
49
package-lock.json
generated
49
package-lock.json
generated
@@ -58,6 +58,7 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
}
|
||||
},
|
||||
@@ -3087,6 +3088,25 @@
|
||||
"opener": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.37.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
|
||||
@@ -11712,6 +11732,18 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
@@ -19234,6 +19266,17 @@
|
||||
"opener": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.37.0"
|
||||
}
|
||||
},
|
||||
"@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
|
||||
@@ -25594,6 +25637,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
"@types/react-syntax-highlighter": "^15.5.3",
|
||||
"@playwright/test": "^1.36.2"
|
||||
}
|
||||
}
|
||||
|
||||
80
playwright.config.ts
Normal file
80
playwright.config.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e-tests/tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { channel: 'chrome', },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { browserName: 'firefox', },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { browserName: 'webkit', },
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 180 * 1000,
|
||||
},
|
||||
});
|
||||
@@ -35,12 +35,12 @@ const Navbar = () => {
|
||||
const [isRefreshingUserState, setIsRefreshingUserState] = useState(false);
|
||||
|
||||
const items = [
|
||||
{ label: <Link to="/peers">Peers</Link>, key: "/peers" },
|
||||
{ label: <Link to="/setup-keys">Setup Keys</Link>, key: "/setup-keys" },
|
||||
{ label: <Link to="/acls">Access Control</Link>, key: "/acls" },
|
||||
{ label: <Link to="/routes">Network Routes</Link>, key: "/routes" },
|
||||
{ label: <Link to="/dns">DNS</Link>, key: "/dns" },
|
||||
{ label: <Link to="/users">Users</Link>, key: "/users" },
|
||||
{ label: <Link data-testid="peers-page" to="/peers">Peers</Link>, key: "/peers" },
|
||||
{ label: <Link data-testid="setup-keys-page" to="/setup-keys">Setup Keys</Link>, key: "/setup-keys" },
|
||||
{ label: <Link data-testid="access-control-page" to="/acls">Access Control</Link>, key: "/acls" },
|
||||
{ label: <Link data-testid="network-routes-page" to="/routes">Network Routes</Link>, key: "/routes" },
|
||||
{ label: <Link data-testid="dns-page"to="/dns">DNS</Link>, key: "/dns" },
|
||||
{ label: <Link data-testid="usersf-page" to="/users">Users</Link>, key: "/users" },
|
||||
{ label: <Link to="/activity">Activity</Link>, key: "/activity" },
|
||||
{ label: <Link to="/settings">Settings</Link>, key: "/settings" },
|
||||
] as ItemType[];
|
||||
|
||||
@@ -39,27 +39,27 @@ export const AddPeerPopup: React.FC<Props> = ({
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: "1",
|
||||
label: <span><Icon component={LinuxSVG}/>Linux</span>,
|
||||
label: <span data-testid="add-peer-modal-linux-tab"><Icon component={LinuxSVG}/>Linux</span>,
|
||||
children: <UbuntuTab/>,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: <span><WindowsFilled/>Windows</span>,
|
||||
label: <span data-testid="add-peer-modal-windows-tab"><WindowsFilled/>Windows</span>,
|
||||
children: <WindowsTab/>,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: <span><AppleFilled/>MacOS</span>,
|
||||
label: <span data-testid="add-peer-modal-mac-tab"><AppleFilled/>MacOS</span>,
|
||||
children: <MacTab/>,
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
label: <span><AndroidFilled/>Android</span>,
|
||||
label: <span data-testid="add-peer-modal-android-tab"><AndroidFilled/>Android</span>,
|
||||
children: <AndroidTab/>,
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
label: <span><Icon component={DockerSVG}/>Docker</span>,
|
||||
label: <span data-testid="add-peer-modal-docker-tab"><Icon component={DockerSVG}/>Docker</span>,
|
||||
children: <DockerTab/>,
|
||||
}
|
||||
];
|
||||
|
||||
@@ -13,7 +13,7 @@ export const AndroidTab = () => {
|
||||
key: 1,
|
||||
title: 'Download and install the application from Google Play Store:',
|
||||
commands: (
|
||||
<a href="https://play.google.com/store/apps/details?id=io.netbird.client" target="_blank">
|
||||
<a data-testid="download-android-button" href="https://play.google.com/store/apps/details?id=io.netbird.client" target="_blank">
|
||||
<Image width="12em" preview={false} style={{marginTop: "5px"}} src={googleplay}/>
|
||||
</a>
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ export const DockerTab = () => {
|
||||
title: "Install Docker",
|
||||
commands: (
|
||||
<Button
|
||||
data-testid="download-docker-button"
|
||||
style={{ marginTop: "5px" }}
|
||||
type="primary"
|
||||
href="https://docs.docker.com/engine/install/"
|
||||
|
||||
@@ -51,6 +51,7 @@ export const LinuxTab = () => {
|
||||
commands: (
|
||||
<Row style={{ paddingTop: "5px" }}>
|
||||
<Button
|
||||
data-testid="download-intel-button"
|
||||
style={{ marginRight: "10px" }}
|
||||
type="primary"
|
||||
href="https://pkgs.netbird.io/macos/amd64"
|
||||
@@ -58,6 +59,7 @@ export const LinuxTab = () => {
|
||||
Download for Intel
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="download-m1-m2-button"
|
||||
style={{ marginRight: "10px" }}
|
||||
type="default"
|
||||
href="https://pkgs.netbird.io/macos/arm64"
|
||||
|
||||
@@ -12,7 +12,7 @@ export const WindowsTab = () => {
|
||||
key: 1,
|
||||
title: 'Download and run Windows installer:',
|
||||
commands: (
|
||||
<Button style={{marginTop: "5px"}} type="primary" href="https://pkgs.netbird.io/windows/x64" target="_blank">Download NetBird</Button>
|
||||
<Button data-testid="download-windows-button" style={{marginTop: "5px"}} type="primary" href="https://pkgs.netbird.io/windows/x64" target="_blank">Download NetBird</Button>
|
||||
),
|
||||
copied: false
|
||||
} as StepCommand,
|
||||
|
||||
@@ -298,7 +298,7 @@ export const AccessControl = () => {
|
||||
setPolicyToAction(record as PolicyDataTable);
|
||||
confirm({
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
title: <span className="font-500">Delete rule {record.name}</span>,
|
||||
title: <span data-testid="confirm-delete-modal-title" className="font-500">Delete rule {record.name}</span>,
|
||||
width: 500,
|
||||
content: (
|
||||
<Space direction="vertical" size="small">
|
||||
|
||||
@@ -757,6 +757,7 @@ export const Peers = () => {
|
||||
Get started by adding one to your network.
|
||||
</Paragraph>
|
||||
<Button
|
||||
data-testid="add-new-peer-button"
|
||||
size={"middle"}
|
||||
type="primary"
|
||||
onClick={() => setAddPeerModalOpen(true)}
|
||||
@@ -934,6 +935,7 @@ export const Peers = () => {
|
||||
}}
|
||||
footer={[]}
|
||||
width={780}
|
||||
data-testid="add-peer-modal"
|
||||
>
|
||||
<AddPeerPopup
|
||||
greeting={!hadFirstRun ? "Hi there!" : ""}
|
||||
|
||||
Reference in New Issue
Block a user