Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ jobs:
flags: unittests
token: ${{ secrets.CODECOV_TOKEN }}

- name: Upload browser test results to Codecov
uses: codecov/codecov-action@v4
if: ${{ !cancelled() }}
with:
files: ./codecov/browsertests.xml
flags: browsertests
token: ${{ secrets.CODECOV_TOKEN }}

- name: Upload functional test coverage results to Codecov
uses: codecov/codecov-action@v4
if: ${{ !cancelled() }}
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ COPY --chown=cmsuser:cmsuser . /home/cmsuser/src

RUN --mount=type=cache,target=/home/cmsuser/.cache/pip,uid=2000 ./install.py cms --devel

RUN playwright install --with-deps chromium

RUN <<EOF
#!/bin/bash -ex
sed 's|/cmsuser:your_password_here@localhost:5432/cmsdb"|/postgres@testdb:5432/cmsdbfortesting"|' \
Expand Down
19 changes: 19 additions & 0 deletions cmstestsuite/browser_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2026 Pasit Sangprachathanarak <ouipingpasit@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Browser-based tests for CWS and AWS."""
49 changes: 49 additions & 0 deletions cmstestsuite/browser_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2026 Pasit Sangprachathanarak <ouipingpasit@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging
import os
import sys
import pytest

logger = logging.getLogger(__name__)


def pytest_configure(config):
"""Initialize CONFIG dictionary before tests run."""
from cmstestsuite import CONFIG

CONFIG["CONFIG_PATH"] = os.path.join(sys.prefix, "etc/cms.toml")

# Allow override via CMS_CONFIG env
if "CMS_CONFIG" in os.environ:
CONFIG["CONFIG_PATH"] = os.environ["CMS_CONFIG"]


@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
"""Configure browser context for tests.

Runs headless by default (for CI), but can be overridden
with pytest --headed flag for local debugging.
"""
return {
**browser_context_args,
"viewport": {"width": 1280, "height": 720},
"ignore_https_errors": True,
}
105 changes: 105 additions & 0 deletions cmstestsuite/browser_tests/runservice.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env bash

set -e

# Start LogService first (needed by AdminWebServer)
echo "Starting LogService..."
cmsLogService 0 &
LOG_PID=$!
sleep 2

# Start AdminWebServer (needed to create contest properly)
echo "Starting AdminWebServer..."
cmsAdminWebServer 0 &
AWS_PID=$!

# Wait for AdminWebServer to be ready
echo "Waiting for AdminWebServer..."
for i in {1..20}; do
if curl -s http://localhost:8889 > /dev/null 2>&1; then
break
fi
if [ $i -eq 20 ]; then
echo "AdminWebServer failed to start"
kill $AWS_PID $LOG_PID 2>/dev/null || true
exit 1
fi
sleep 1
done

# Create admin and contest via FunctionalTestFramework (creates proper main_group)
echo "Setting up admin and contest..."
CONTEST_ID=$(python3 << 'EOF'
import datetime
import os
import sys

from cms import TOKEN_MODE_FINITE
from cmscommon.datetime import get_system_timezone
from cmstestsuite import CONFIG
from cmstestsuite.functionaltestframework import FunctionalTestFramework

CONFIG["CONFIG_PATH"] = os.path.join(sys.prefix, "etc/cms.toml")
if "CMS_CONFIG" in os.environ:
CONFIG["CONFIG_PATH"] = os.environ["CMS_CONFIG"]

framework = FunctionalTestFramework()
framework.initialize_aws()

start_time = datetime.datetime.utcnow()
stop_time = start_time + datetime.timedelta(hours=2)

contest_id, _ = framework.add_contest(
name="test_contest",
description="Browser test contest",
languages=["C++17 / g++"],
allow_password_authentication="checked",
start=start_time.strftime("%Y-%m-%d %H:%M:%S.%f"),
stop=stop_time.strftime("%Y-%m-%d %H:%M:%S.%f"),
timezone=get_system_timezone(),
allow_user_tests="checked",
token_mode=TOKEN_MODE_FINITE,
token_max_number="100",
token_min_interval="0",
token_gen_initial="100",
token_gen_number="0",
token_gen_interval="1",
token_gen_max="100",
)
print(contest_id)
EOF
)

echo "Created contest with ID: $CONTEST_ID"

# Start ContestWebServer with the proper contest
echo "Starting ContestWebServer for contest $CONTEST_ID..."
cmsContestWebServer -c $CONTEST_ID 0 &
CWS_PID=$!

# Wait for ContestWebServer to be ready
echo "Waiting for ContestWebServer..."
for i in {1..20}; do
if curl -s http://localhost:8888 > /dev/null 2>&1; then
echo "Services are ready!"
break
fi
if [ $i -eq 20 ]; then
echo "ContestWebServer failed to start"
kill $CWS_PID $AWS_PID $LOG_PID 2>/dev/null || true
exit 1
fi
sleep 1
done

# Run the browser tests
echo "Running browser tests..."
pytest cmstestsuite/browser_tests/ "$@"
TEST_EXIT=$?

# Cleanup
echo "Stopping services..."
kill $CWS_PID $AWS_PID $LOG_PID 2>/dev/null || true
wait 2>/dev/null || true

exit $TEST_EXIT
28 changes: 28 additions & 0 deletions cmstestsuite/browser_tests/test_aws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2026 Pasit Sangprachathanarak <ouipingpasit@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import pytest


def test_aws_responds(page):
"""Test that AWS responds on port 8889."""
# Navigate to AWS
page.goto("http://localhost:8889")

# Just verify page loads (200 status)
assert page.url.startswith("http://localhost:8889")
28 changes: 28 additions & 0 deletions cmstestsuite/browser_tests/test_cws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2026 Pasit Sangprachathanarak <ouipingpasit@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import pytest


def test_cws_responds(page):
"""Test that CWS responds on port 8888."""
# Navigate to CWS
page.goto("http://localhost:8888")

# Just verify page loads (200 status)
assert page.url.startswith("http://localhost:8888")
7 changes: 4 additions & 3 deletions docker/_cms-test-internal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ cmsInitDB
pytest --cov . --cov-report xml:codecov/unittests.xml --junitxml=codecov/junit.xml -o junit_family=legacy
UNIT=$?

dropdb --host=testdb --username=postgres cmsdbfortesting
createdb --host=testdb --username=postgres cmsdbfortesting
cmsInitDB

bash cmstestsuite/browser_tests/runservice.sh --junitxml=codecov/browsertests.xml
BROWSER=$?

cmsRunFunctionalTests -v --coverage codecov/functionaltests.xml
FUNC=$?

# This check is needed because otherwise failing unit tests aren't reported in
# the CI as long as the functional tests are passing. Ideally we should get rid
# of `cmsRunFunctionalTests` and make those tests work with pytest so they can
# be auto-discovered and run in a single command.
if [ $UNIT -ne 0 ] || [ $FUNC -ne 0 ]
if [ $UNIT -ne 0 ] || [ $BROWSER -ne 0 ] || [ $FUNC -ne 0 ]
then
exit 1
else
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ devel = [
"coverage>=4.5,<7.10",
"pytest",
"pytest-cov",
"pytest-playwright",

# Only for building documentation
# XXX: The version of Sphinx needed to build our documentation
Expand Down
Loading