Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cypress end to end tests #725

Merged
merged 15 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
33 changes: 25 additions & 8 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
name: E2E Tests

on:
workflow_dispatch # disabled pending db seeding
# push:
# branches: [ main ]
# pull_request:
# branches: [ main ]
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: build containers and run cypress e2e tests
# TODO: seed the database with a sparse(r) dump so there are pages to test..

- name: deploy services in e2e mode
run: make e2e

- name: run e2e tests
uses: cypress-io/github-action@v6
with:
working-directory: e2e
wait-on: "http://localhost:8000"
wait-on-timeout: 300

- name: display service logs
if: failure()
run: docker compose -f docker-compose.yml -f e2e.yml logs

- name: upload cypress videos
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-videos
path: e2e/cypress/videos
24 changes: 18 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ release-version: .env
.PHONY: docker-compose.yml
docker-compose.yml: base.yml dev.yml staging.yml prod.yml config.mk $(PGPASS_PATH) release-version
case "$(DEPLOY_ENVIRONMENT)" in \
dev|staging|e2e) docker compose -f base.yml -f $(DEPLOY_ENVIRONMENT).yml config > docker-compose.yml;; \
dev|staging) docker compose -f base.yml -f $(DEPLOY_ENVIRONMENT).yml config > docker-compose.yml;; \
prod) docker compose -f base.yml -f staging.yml -f $(DEPLOY_ENVIRONMENT).yml config > docker-compose.yml;; \
*) echo "invalid environment. must be either dev, staging or prod" 1>&2; exit 1;; \
esac
Expand Down Expand Up @@ -138,9 +138,21 @@ clean_deploy: clean
test: build
docker compose run --rm server /code/deploy/test.sh

# e2e testing setup

E2E_SHARED_DIR=${DOCKER_SHARED_DIR}/e2e
E2E_BACKUPS_PATH=${E2E_SHARED_DIR}/backups
E2E_REPO_PATH=${E2E_BACKUPS_PATH}/repo

$(E2E_REPO_PATH):
mkdir -p $(E2E_BACKUPS_PATH)
wget -c ${BORG_REPO_URL} -P $(E2E_BACKUPS_PATH)
tar -Jxf $(E2E_BACKUPS_PATH)/repo.tar.xz -C $(E2E_BACKUPS_PATH)

.PHONY: e2e
e2e: DEPLOY_ENVIRONMENT=e2e
e2e: build
docker compose run server inv collectstatic
docker compose run --rm e2e npm run test
docker compose down
e2e: docker-compose.yml secrets $(DOCKER_SHARED_DIR) $(E2E_REPO_PATH)
docker compose -f docker-compose.yml -f e2e.yml up -d --build
docker compose -f docker-compose.yml -f e2e.yml exec server bash -c "\
inv borg.restore --force && \
./manage.py migrate && \
inv prepare"
4 changes: 2 additions & 2 deletions django/core/jinja2/common.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@
<form class="mb-3" action="{{ search_url }}" method="get">
<label class="sr-only" for="search-query">Search</label>
<div class="input-group">
<input id="search-query" class="form-control" name="query" type="search" value=""
<input id="search-query" data-cy="search-bar" class="form-control" name="query" type="search" value=""
placeholder="{{ placeholder }}">
<button type="submit" class="btn btn-primary">
<button type="submit" data-cy="search-button" class="btn btn-primary">
<i class="fas fa-search px-1"></i>
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion django/core/jinja2/core/events/list.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


{% macro render_event(item, detail_url_name) %}
<div class="card mb-3">
<div class="card mb-3" data-cy="event result">
<div class='card-body'>
<div class='row'>
<div class='col-8'>
Expand Down
2 changes: 1 addition & 1 deletion django/core/jinja2/core/jobs/list.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% endblock ogp_tags %}

{% macro render_job(item, detail_url_name) %}
<div class="card mb-3">
<div class="card mb-3" data-cy="job-result">
<div class='card-body'>
<div class='row'>
<div class='col-8'>
Expand Down
4 changes: 2 additions & 2 deletions django/core/jinja2/core/member_profiles/retrieve.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
{% endautoescape %}
{% endif %}
</div>
<div class='tab-pane' id='code' role='tabpanel'>
<div class="codebase result" class='tab-pane' id='code' role='tabpanel'>
{% for codebase in codebases %}
{{ render_codebase_result(codebase) }}
{% else %}
Expand Down Expand Up @@ -171,7 +171,7 @@
<div class='card-metadata'>
<div class='card-body'>
{% if has_change_perm %}
<a href="{{ profile.get_edit_url() }}" class='btn btn-primary w-100 my-1'>
<a href="{{ profile.get_edit_url() }}" class='btn btn-primary w-100 my-1' data-cy="edit-profile">
Edit Profile</a>
<a href="{{ url('library:codebase-add') }}" class="btn btn-secondary w-100 my-1">
Publish a model
Expand Down
24 changes: 22 additions & 2 deletions django/core/settings/e2e.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
from .dev import *
from .test import *

DEPLOY_ENVIRONMENT = Environment.TEST
DEBUG = True

DJANGO_VITE_DEV_MODE = False

SHARE_DIR = path.realpath("/shared/e2e")
LIBRARY_ROOT = path.join(SHARE_DIR, "library")
LIBRARY_PREVIOUS_ROOT = path.join(SHARE_DIR, ".latest")
REPOSITORY_ROOT = path.join(BASE_DIR, "repository")
BACKUP_ROOT = path.join(SHARE_DIR, "backups")
BORG_ROOT = path.join(BACKUP_ROOT, "repo")
EXTRACT_ROOT = path.join(SHARE_DIR, "extract")
MEDIA_ROOT = path.join(SHARE_DIR, "media")

DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("DB_NAME"),
"USER": os.getenv("DB_USER"),
"PASSWORD": read_secret("db_password", os.getenv("DB_PASSWORD")),
"HOST": "e2edb",
"PORT": os.getenv("DB_PORT"),
}
}
13 changes: 9 additions & 4 deletions django/curator/invoke_tasks/borg.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,19 @@ def restore_database(

@task()
def restore(
ctx, repo=settings.BORG_ROOT, archive=None, target_database=db._DEFAULT_DATABASE
ctx,
repo=settings.BORG_ROOT,
archive=None,
target_database=db._DEFAULT_DATABASE,
force=False,
):
"""Restore the library files, media files and database to the state given in the borg repo at path REPO
using archive ARCHIVE. The target_database argument is for testing so a different database can be used to
make sure the database is getting restored properly"""
confirm(
"Are you sure you want to restore the database and all file content (y/n)? "
)
if not force:
confirm(
"Are you sure you want to restore the database and all file content (y/n)? "
)
with tempfile.TemporaryDirectory(dir=settings.SHARE_DIR) as working_directory:
_restore(
ctx,
Expand Down
4 changes: 2 additions & 2 deletions django/curator/invoke_tasks/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def create_pgpass_file(ctx, db_key=_DEFAULT_DATABASE, force=False):
if os.path.isfile(pgpass_path) and not force:
return
with open(pgpass_path, "w+") as pgpass:
pgpass.write("db:*:*:{db_user}:{db_password}\n".format(**db_config))
pgpass.write("{db_host}:*:*:{db_user}:{db_password}\n".format(**db_config))
ctx.run("chmod 0600 ~/.pgpass")


Expand Down Expand Up @@ -132,7 +132,7 @@ def restore_from_dump(
cat_cmd = "zcat"
drop(ctx, database=target_database, create=True)
ctx.run(
"{cat_cmd} {dumpfile} | psql -w -q -o restore-from-dump-log.txt -h db {db_name} {db_user}".format(
"{cat_cmd} {dumpfile} | psql -w -q -o restore-from-dump-log.txt -h {db_host} {db_name} {db_user}".format(
cat_cmd=cat_cmd, dumpfile=dumpfile, **db_config
),
echo=True,
Expand Down
2 changes: 1 addition & 1 deletion django/library/jinja2/library/codebases/macros.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
{% endmacro %}

{% macro render_codebase_result(codebase) %}
<div class="search-result">
<div data-cy="codebase-search-result" class="search-result">
<div class="row">
<div class="col-md-12 col-lg-3">
<a href="{{ codebase.get_absolute_url() }}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,18 @@
{% if release.live %}
{% if invite %}
{# direct release download #}
<a id="releaseDownload" class='btn btn-primary my-1 w-100' data-name="download" rel='nofollow' href="{{ release.get_download_url() }}">
<a id="releaseDownload" data-cy="release-version" class='btn btn-primary my-1 w-100' data-name="download" rel='nofollow' href="{{ release.get_download_url() }}">
<i class='fas fa-download'></i> Download Version {{ release.version_number }}
</a>
{% else %}
{# use survey form #}
<div id='download-form' data-user-data="{{ get_download_request_metadata(request.user) }}"
<div id='download-form' data-cy="release-version" data-user-data="{{ get_download_request_metadata(request.user) }}"
data-version-number="{{ release.version_number }}"
data-identifier="{{ codebase.identifier }}"></div>
{% endif %}
{% else %}
{# direct review archive download #}
<a rel='nofollow' class='btn btn-primary my-1 w-100' href="{{ release.get_review_download_url() }}">
<a rel='nofollow' data-cy="release-version" class='btn btn-primary my-1 w-100' href="{{ release.get_review_download_url() }}">
Download for Review
</a>
{% endif %}
Expand Down
53 changes: 35 additions & 18 deletions e2e.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
services:
e2e:
image: comses/cypress
build: e2e
environment:
- CYPRESS_baseUrl=http://server:8000
db:
image: alpine # disable the normal db container
command: tail -f /dev/null
healthcheck:
disable: true
e2edb:
image: postgis/postgis:15-3.4
secrets:
- db_password # re-using the same db password
volumes:
- ./e2e/cypress:/code/cypress
- ./e2e/cypress.config.js:/code/cypress.config.js
- ./django/deploy/wait-for-it.sh:/code/wait-for-it.sh
depends_on:
- server
- ./build/secrets/db_password:/run/secrets/db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 30s
timeout: 5s
retries: 5
environment:
POSTGRES_USER: "${DB_USER}"
POSTGRES_DB: "${DB_NAME}"
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
vite:
volumes:
- ./frontend:/code
command: ["npm", "run", "build"]
environment:
NODE_ENV: "e2e"
server:
image: comses/server:dev
volumes:
- ./django:/code
- ./docs:/docs
depends_on:
db:
condition: service_started
e2edb:
condition: service_healthy
elasticsearch:
condition: service_started
redis:
condition: service_started
vite:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
environment:
DJANGO_SETTINGS_MODULE: "core.settings.e2e"
ports:
- "127.0.0.1:8000:8000"
12 changes: 12 additions & 0 deletions e2e/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": true,
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid"
}
14 changes: 0 additions & 14 deletions e2e/Dockerfile

This file was deleted.

4 changes: 2 additions & 2 deletions e2e/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: "http://localhost:8000",
specPattern: ["cypress/e2e/**/*.spec.ts"],
specPattern: ["cypress/tests/**/*.spec.ts"],
supportFile: false,
screenshotOnRunFailure: false,
video: false,
video: true,
}
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/cypress/fixtures/codebase/testCodebase.zip
Binary file not shown.
22 changes: 22 additions & 0 deletions e2e/cypress/fixtures/codebase/testCodebase/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#generated from chatgpt

[tool.poetry]
name = "logistic-growth-model"
version = "0.1.0"
description = "A simple logistic growth model simulation"
authors = ["Your Name <[email protected]>"]
license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.8"
numpy = "^1.21.0"
matplotlib = "^3.4.2"

[tool.poetry.dev-dependencies]
pytest = "^6.2.4"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

33 changes: 33 additions & 0 deletions e2e/cypress/fixtures/codebase/testCodebase/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#generated from chatgpt

import numpy as np
import matplotlib.pyplot as plt

# Logistic growth model parameters
r = 0.1 # Growth rate
K = 1000 # Carrying capacity
P0 = 10 # Initial population
t_max = 100 # Time duration
dt = 1 # Time step

# Time array
time = np.arange(0, t_max, dt)

# Population array
population = np.zeros_like(time)
population[0] = P0

# Logistic growth model calculation
for t in range(1, len(time)):
population[t] = population[t-1] + r * population[t-1] * (1 - population[t-1] / K) * dt

# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(time, population, label='Population')
plt.xlabel('Time')
plt.ylabel('Population')
plt.title('Logistic Growth Model')
plt.legend()
plt.grid(True)
plt.show()

Loading
Loading