Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: 'WrongSecrets CTF Party: E2E Tests'
on:
pull_request:
branches: [ main ]
jobs:
e2e-test:
name: Run Cypress E2E Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Start Minikube
uses: medyagh/setup-minikube@latest
with:
driver: docker
cpus: '2'
memory: '8000'
kubernetes-version: 'v1.32.0'
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install yq
run: sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq
- name: Run Deployment Script
run: ./build-and-deploy-minikube.sh
Copy link
Collaborator

@commjoen commjoen Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems it is still waiting here as the script will never end. Instead it might be better to run this in the background (e.g. ./build-and-deploy-minikube.sh & and then do a poll:

Suggested change
run: ./build-and-deploy-minikube.sh
run: |
./build-and-deploy-minikube.sh &
pause 10
kubectl wait --for=condition=ready pod -l app=wrongsecrets-balancer --timeout=2s

- name: Wait for deployment to be ready
run: kubectl wait --for=condition=available deployment/wrongsecrets-balancer --timeout=300s
- name: Install Cypress and Run Tests
uses: cypress-io/github-action@v6
with:
working-directory: wrongsecrets-balancer
wait-on: 'http://localhost:3000'
wait-on-timeout: 300
browser: chrome
headless: true
2 changes: 1 addition & 1 deletion build-and-deploy-minikube.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ source ./scripts/check-available-commands.sh
checkCommandsAvailable helm docker kubectl yq minikube

minikube delete
minikube start --cpus=8 --memory=12000MB --network-plugin=cni --cni=calico --driver=docker --kubernetes-version=1.32.0
minikube start --cpus=2 --memory=8000MB --network-plugin=cni --cni=calico --driver=docker --kubernetes-version=1.32.0
eval $(minikube docker-env)
./build-and-deploy.sh

Expand Down
38 changes: 38 additions & 0 deletions scripts/run-e2e-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
# This script will exit immediately if any command fails
set -e

echo "--- Starting Minikube ---"
minikube start --cpus=2 --memory=8000MB --driver=docker --network-plugin=cni --cni=calico --kubernetes-version=1.32.0

echo "--- Updating Helm Repositories ---"
helm repo update

echo "--- Deploying Application ---"
helm upgrade --install wrongsecrets ./helm/wrongsecrets-ctf-party

echo "--- Waiting for Balancer Deployment to be Ready ---"
kubectl wait --for=condition=available deployment/wrongsecrets-balancer --timeout=5m

echo "--- Starting Port Forward in Background ---"
kubectl port-forward service/wrongsecrets-balancer 3000:3000 &
# Store the ID of the background process
PORT_FORWARD_PID=$!

echo "--- Waiting for Port Forward to establish... ---"
sleep 5

echo "--- Getting Admin Password ---"
# Note: We get the password here for the test to use it
ADMIN_PASSWORD=$(kubectl get secrets wrongsecrets-balancer-secret -o=jsonpath='{.data.adminPassword}' | base64 --decode)
export CYPRESS_ADMIN_PASSWORD=$ADMIN_PASSWORD

echo "--- Running Cypress Tests ---"
# Navigate to the correct directory to run the tests
cd wrongsecrets-balancer
# Run Cypress tests headlessly
npx cypress run

echo "--- Cleaning up port-forward process ---"
# Stop the background port-forward process
kill $PORT_FORWARD_PID
7 changes: 7 additions & 0 deletions wrongsecrets-balancer/cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
};
22 changes: 22 additions & 0 deletions wrongsecrets-balancer/cypress/e2e/admin_login.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
describe('Admin Login', () => {
it('should allow the admin to log in through the main page', () => {
// NOTE: The admin password changes every time you deploy.
// You must get the new password from the terminal before running this test.
const adminPassword = 'RSX9I94K';

// Visit the homepage to log in.
cy.visit('http://localhost:3000');

// Type "admin" as the team name and click the button.
cy.get('[data-test-id="teamname-input"]').type('admin');
cy.get('[data-test-id="create-join-team-button"]').click();

// On the next page, type the admin password.
cy.get('[data-test-id="passcode-input"]').type(adminPassword);
cy.contains('button', 'Join Team').click();

// Verify that the admin page has loaded. We give it a longer timeout (10 seconds)
// because the list of teams might take a moment to load from the server.
cy.contains('Active Teams', { timeout: 10000 }).should('be.visible');
});
});
37 changes: 37 additions & 0 deletions wrongsecrets-balancer/cypress/e2e/team_workflow.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
describe('Team Creation and Joining Workflow', () => {
it('should create a team, then allow a user to join it with the passcode', () => {
// Generates a short, unique team name that is under the 16-character limit.
const teamName = `ctf-${Date.now().toString().slice(-9)}`;

// Set up a "spy" to intercept the network request that creates the team.
cy.intercept('POST', `/balancer/teams/${teamName}/join`).as('createTeamRequest');

// === PART 1: CREATE THE TEAM ===
cy.visit('http://localhost:3000');
cy.get('[data-test-id="teamname-input"]').type(teamName);
cy.get('[data-test-id="create-join-team-button"]').click();

// === PART 2: CAPTURE PASSCODE & JOIN ===
// Wait for the network request to finish and get the passcode from its response data.
cy.wait('@createTeamRequest').then((interception) => {
const passcode = interception.response.body.passcode;

// Now that we have the real passcode, go back to the homepage.
cy.visit('http://localhost:3000');

// Enter the same unique team name again.
cy.get('[data-test-id="teamname-input"]').type(teamName);
cy.get('[data-test-id="create-join-team-button"]').click();

// On the "Joining team" page, type the real passcode we captured.
cy.get('[data-test-id="passcode-input"]').type(passcode);
cy.contains('button', 'Join Team').click();

// === PART 3: FINAL VERIFICATION (with a long timeout) ===
// Instead of waiting for a network call, we wait directly for the final button to appear.
// We give it up to 2 minutes (120000ms) for the backend instance to get ready.
cy.contains('Start Hacking', { timeout: 120000 }).should('be.visible');
cy.contains('Start your Webtop').should('be.visible');
});
});
});
5 changes: 5 additions & 0 deletions wrongsecrets-balancer/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
25 changes: 25 additions & 0 deletions wrongsecrets-balancer/cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
17 changes: 17 additions & 0 deletions wrongsecrets-balancer/cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'
Loading