diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index e6287f3a0..842f9d496 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -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
diff --git a/Makefile b/Makefile
index 4e584b73c..4aebccd17 100644
--- a/Makefile
+++ b/Makefile
@@ -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
@@ -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"
diff --git a/django/core/settings/e2e.py b/django/core/settings/e2e.py
index 6ba62d430..a7bc69622 100644
--- a/django/core/settings/e2e.py
+++ b/django/core/settings/e2e.py
@@ -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"),
+ }
+}
diff --git a/django/curator/invoke_tasks/borg.py b/django/curator/invoke_tasks/borg.py
index 56408c564..2db1922ad 100644
--- a/django/curator/invoke_tasks/borg.py
+++ b/django/curator/invoke_tasks/borg.py
@@ -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,
diff --git a/django/curator/invoke_tasks/database.py b/django/curator/invoke_tasks/database.py
index b2427073c..43035704e 100644
--- a/django/curator/invoke_tasks/database.py
+++ b/django/curator/invoke_tasks/database.py
@@ -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")
@@ -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,
diff --git a/e2e.yml b/e2e.yml
index 436b47f05..8b406f2f1 100644
--- a/e2e.yml
+++ b/e2e.yml
@@ -1,27 +1,43 @@
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: null
+ 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"
diff --git a/e2e/Dockerfile b/e2e/Dockerfile
deleted file mode 100644
index ec2e8c569..000000000
--- a/e2e/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM cypress/base:18.14.1
-WORKDIR /code
-
-# only install dependencies if package.json or package-lock.json has changed
-COPY package.json .
-COPY package-lock.json .
-
-# suppress most of the cypress messages
-ENV CI=1
-
-RUN npm install
-
-# verify that cypress is installed correctly
-RUN npm run cy:run -- verify
diff --git a/e2e/cypress.config.js b/e2e/cypress.config.js
index cf0cec0be..6906d32df 100644
--- a/e2e/cypress.config.js
+++ b/e2e/cypress.config.js
@@ -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,
}
});
diff --git a/e2e/cypress/fixtures/codebase/codebasetestimage.png b/e2e/cypress/fixtures/codebase/codebasetestimage.png
new file mode 100644
index 000000000..96a5268e8
Binary files /dev/null and b/e2e/cypress/fixtures/codebase/codebasetestimage.png differ
diff --git a/e2e/cypress/fixtures/codebase/testNarrativeDocumentation.txt b/e2e/cypress/fixtures/codebase/testNarrativeDocumentation.txt
new file mode 100644
index 000000000..bff092200
--- /dev/null
+++ b/e2e/cypress/fixtures/codebase/testNarrativeDocumentation.txt
@@ -0,0 +1 @@
+//test narative documentation
diff --git a/e2e/cypress/fixtures/codebase/testSimulationOutput.txt b/e2e/cypress/fixtures/codebase/testSimulationOutput.txt
new file mode 100644
index 000000000..9f1541be1
--- /dev/null
+++ b/e2e/cypress/fixtures/codebase/testSimulationOutput.txt
@@ -0,0 +1 @@
+//testSimulationOutput
diff --git a/e2e/cypress/fixtures/codebase/testSourceCode.txt b/e2e/cypress/fixtures/codebase/testSourceCode.txt
new file mode 100644
index 000000000..003a0b85d
--- /dev/null
+++ b/e2e/cypress/fixtures/codebase/testSourceCode.txt
@@ -0,0 +1,2 @@
+//test source code
+
diff --git a/e2e/cypress/fixtures/codebase/testUploadData.txt b/e2e/cypress/fixtures/codebase/testUploadData.txt
new file mode 100644
index 000000000..89786372e
--- /dev/null
+++ b/e2e/cypress/fixtures/codebase/testUploadData.txt
@@ -0,0 +1 @@
+//test upload data
diff --git a/e2e/cypress/tests/beforeLoginTests.spec.ts b/e2e/cypress/tests/beforeLoginTests.spec.ts
new file mode 100644
index 000000000..44ec659aa
--- /dev/null
+++ b/e2e/cypress/tests/beforeLoginTests.spec.ts
@@ -0,0 +1,19 @@
+//test ability to browse/download codebases
+describe("Browse Codebases", () => {
+ it("should browse the model library and be able to enter a search query", () => {
+ cy.visit("/"); // visit homepage
+ cy.visit("/codebases");
+ assert(cy.get("h1").contains("Computational Model Library"));
+ });
+});
+
+//download codebase
+describe("Download Codebases", () => {
+ it("should download a codebase", () => {
+ // Navigate to the first 'search-result', find the 'a' tag within the 'title' div, and click it
+ cy.get(".search-result", { timeout: 10000 })
+ .first()
+ .find(".title a")
+ .click();
+ });
+});
diff --git a/e2e/cypress/tests/codebase.spec.ts b/e2e/cypress/tests/codebase.spec.ts
new file mode 100644
index 000000000..4cf706cac
--- /dev/null
+++ b/e2e/cypress/tests/codebase.spec.ts
@@ -0,0 +1,121 @@
+import { loginBeforeEach } from "./setup.spec";
+import "cypress-file-upload";
+
+//login
+describe("Login", () => {
+ it("should log into comses homepage with test user", () => {
+ loginBeforeEach("test_user", "123456");
+ });
+});
+
+
+describe("Visit codebases page", () => {
+ //codebases PAGE
+
+ it("should visit the codebases page", () => {
+ cy.visit("/codebases");
+ assert(cy.get("h1").contains("Computational Model Library"));
+ });
+
+ it("should be able to download a codebase", () => {
+ cy.visit("/codebases");
+ cy.get(".search-result").first().find("a").first().click();
+ cy.get("#djHideToolBarButton").click();
+ cy.get('button.btn.btn-primary.my-1.w-100[rel="nofollow"]').click();
+ cy.get("#form-field-industry").select("College/University");
+ cy.get("#form-field-affiliation").type("Arizona State University{enter}", {
+ force: true,
+ });
+ cy.get("#form-field-reason").select("Research");
+ cy.get(
+ 'button[type="submit"][form="download-request-form"].btn.btn-success'
+ ).click();
+ });
+
+ it("should be able to upload a codebase", () => {
+ loginBeforeEach("test_user", "123456");
+ cy.visit("/codebases");
+ assert(cy.get("h1").contains("Computational Model Library"));
+ cy.get("#djHideToolBarButton").click();
+ cy.contains("Publish a model").click();
+ cy.get('[data-cy="codebase title"]').type("Codebase Title");
+ cy.get('[data-cy="codebase description"]').type("Codebase Description");
+ cy.get('[data-cy="codebase replication-text"]').type(
+ "Codebase Replication Text"
+ );
+ cy.get('[data-cy="codebase associated publications"]').type(
+ "Codebase Associated Publications"
+ );
+ cy.get('[data-cy="codebase references"]').type("Codebase References");
+ cy.get('[data-cy="next"]').click();
+ //add images
+ cy.get('[data-cy="add image"]').click(); //add image
+ cy.get('[data-cy="upload-image"]')
+ .first()
+ .selectFile("cypress/fixtures/codebase/codebasetestimage.png", {
+ force: true,
+ });
+ cy.get('button.btn.btn-outline-gray[data-bs-dismiss="modal"]')
+ .get("button")
+ .first()
+ .click({ force: true });
+ cy.get('button.btn.btn-outline-gray[data-bs-dismiss="modal"]')
+ .should("be.visible")
+ .and("not.be.disabled")
+ .first()
+ .click({ force: true });
+ cy.get("body").click(0, 0);
+ cy.get("body").click(0, 0); //FIX: find a more precise way of closing the image upload modal
+
+ //add source code files
+ cy.get('[data-cy="upload-code"]')
+ .first()
+ .selectFile("cypress/fixtures/codebase/testSourceCode.txt", {
+ force: true,
+ });
+ cy.get('[data-cy="upload-docs"]')
+ .first()
+ .selectFile("cypress/fixtures/codebase/testNarrativeDocumentation.txt", {
+ force: true,
+ });
+ cy.get('[data-cy="upload-data"]')
+ .first()
+ .selectFile("cypress/fixtures/codebase/testUploadData.txt", {
+ force: true,
+ });
+ cy.get('[data-cy="upload-results"]')
+ .first()
+ .selectFile("cypress/fixtures/codebase/testSimulationOutput.txt", {
+ force: true,
+ });
+ cy.get('[data-cy="add metadata"]').click();
+ cy.get('[data-cy="release-notes"] textarea').type("Release notes");
+ cy.get('[data-cy="embargo-end-date"]').click();
+ cy.get('[data-cy="embargo-end-date"]').contains("29").click();
+ cy.get('[data-cy="operating-system"] select').select(
+ "Operating System Independent"
+ );
+ cy.get('[data-cy="software-frameworks"]').type("NetLogo {enter} ");
+ cy.get("body").click(0, 0);
+ cy.get('[data-cy="programming-languages"').type("Net Logo {enter}");
+ cy.get("body").click(0, 0);
+ cy.get('[data-cy="license"] .multiselect__select').click();
+ cy.get('[data-cy="license"] .multiselect__element')
+ .contains("GPL-2.0")
+ .click();
+ cy.get('[data-cy="save-and-continue"]').click();
+ cy.get('button.btn.btn-danger[rel="nofollow"]').click();
+ cy.get('button[type="submit"].btn.btn-danger[form="publish-form"]').click();
+ });
+
+ it("should verify that the codebase was uploaded correctly", () => {
+ cy.visit("/codebases");
+ cy.get(".search-result").first().find("a").first().click();
+ assert(cy.get("h1").contains("Codebase Title"));
+ assert(cy.get("p").contains("Codebase Description"));
+ assert(cy.get("p").contains("Codebase Replication Text"));
+ assert(cy.get("p").contains("Codebase Associated Publications"));
+ assert(cy.get("p").contains("Codebase References"));
+
+ })
+});
\ No newline at end of file
diff --git a/e2e/cypress/tests/event.spec.ts b/e2e/cypress/tests/event.spec.ts
new file mode 100644
index 000000000..a9798bd96
--- /dev/null
+++ b/e2e/cypress/tests/event.spec.ts
@@ -0,0 +1,55 @@
+import { loginBeforeEach } from "./setup.spec";
+import "cypress-file-upload";
+
+
+describe("Visit events page", () => {
+ //EVENTS PAGE
+
+ it("should visit the events page", () => {
+ cy.visit("/events");
+ assert(cy.get("h1").contains("Community Events"));
+ cy.get(".card-body").first().find("a").first().click();
+ assert(cy.get("h1").contains("Title"));
+ });
+
+ it("should be able to search for a specific event", () => {
+ cy.visit("/events");
+ cy.get('input[class="form-control"]').type(
+ "Call for applications to organize a 2022 CECAM-Lorentz funded workshop on modeling"
+ );
+ cy.get("button.btn.btn-primary").click();
+ });
+
+ it("should be able to submit an event", () => {
+ loginBeforeEach("test_user", "123456");
+ cy.visit("/events");
+ cy.get("#djHideToolBarButton").click();
+ cy.get(".text-white").first().click({ force: true });
+ cy.get('[data-cy="event title"]').type("Title");
+ cy.get('[data-cy="event location"]').type("Location");
+ cy.get('[data-cy="event start date"]').first().click(); //event start date
+ cy.get('[data-cy="event start date"]').contains("22").click();
+ cy.get('[data-cy="event end date"]').first().click(); //event end date
+ cy.get('[data-cy="event end date"]').contains("27").click();
+ cy.get('[data-cy="early registration deadline"]').first().click(); //early registration deadline
+ cy.get('[data-cy="early registration deadline"]').contains("22").click();
+ cy.get('[data-cy="registration deadline"]').first().click(); //registration deadline
+ cy.get('[data-cy="registration deadline"]').contains("25").click();
+ cy.get('[data-cy="submission deadline"]').first().click(); //submission deadline
+ cy.get('[data-cy="submission deadline"]').contains("22").click();
+ cy.get('[data-cy="description"]').type("Description");
+ cy.get('[data-cy="summary"]').type("Summary");
+ cy.get('[data-cy="external url"]').type("https://www.comses.net/");
+ cy.get('[data-cy="create button"]').click();
+ });
+
+ it("should be able to verify event was submitted correctly", () => {
+ cy.visit("/events");
+ cy.get("#djHideToolBarButton").click();
+ assert(cy.get("h1").contains("Community Events"));
+ cy.get(".card-body").first().find("a").first().click();
+ assert(cy.get("h1").contains("Title"));
+ assert(cy.get("p").contains("Description"));
+ assert(cy.get("p").contains("Location"));
+ });
+ });
\ No newline at end of file
diff --git a/e2e/cypress/tests/job.spec.ts b/e2e/cypress/tests/job.spec.ts
new file mode 100644
index 000000000..6382a2851
--- /dev/null
+++ b/e2e/cypress/tests/job.spec.ts
@@ -0,0 +1,35 @@
+import { loginBeforeEach } from "./setup.spec";
+import "cypress-file-upload";
+
+describe("Visit jobs page", () => {
+ //JOBS PAGE
+
+ it("should visit the jobs page", () => {
+ cy.visit("/jobs");
+ assert(cy.get("h1").contains("Jobs & Appointments"));
+ });
+
+ it("should be able to submit a job posting", () => {
+ loginBeforeEach("test_user", "123456");
+ cy.visit("/jobs");
+ cy.get("#djHideToolBarButton").click();
+ cy.get(".text-white").first().click({ force: true });
+ cy.get('[data-cy="job title"]').type("Job Title");
+ cy.get('[data-cy="job description"]').type("Job Description");
+ cy.get('[data-cy="job summary"]').type("Job Summary");
+ cy.get('[data-cy="external url"]').type("https://www.comses.net/");
+ cy.get('[data-cy="application deadline"]').first().click();
+ cy.get('[data-cy="application deadline"]').contains("29").click();
+ cy.get('[data-cy="create button"]').click();
+ });
+
+ it("should be able to verify job was submitted correctly", () => {
+ cy.visit("/jobs");
+ cy.get("#djHideToolBarButton").click();
+ assert(cy.get("h1").contains("Jobs & Appointments"));
+ cy.get(".card-body").first().find("a").first().click();
+ assert(cy.get("h1").contains("Job Title"));
+ assert(cy.get("p").contains("Job Description"));
+ });
+ });
+
\ No newline at end of file
diff --git a/e2e/cypress/tests/setup.spec.ts b/e2e/cypress/tests/setup.spec.ts
new file mode 100644
index 000000000..4876a3eb3
--- /dev/null
+++ b/e2e/cypress/tests/setup.spec.ts
@@ -0,0 +1,8 @@
+//contains setup function for smoke tests
+export const loginBeforeEach = (username, password) => {
+ cy.visit("/accounts/login/?next=/");
+ assert(cy.get("h1").contains("Sign In")); //simply goes to sign in page
+ cy.get('input[name="login"]').type(username);
+ cy.get('input[name="password"]').type(password);
+ cy.contains("button", "Sign In").click();
+};
diff --git a/e2e/cypress/tests/user.spec.ts b/e2e/cypress/tests/user.spec.ts
new file mode 100644
index 000000000..6b3cdcf37
--- /dev/null
+++ b/e2e/cypress/tests/user.spec.ts
@@ -0,0 +1,20 @@
+import { loginBeforeEach } from "./setup.spec";
+import "cypress-file-upload";
+
+//login
+describe("User tests", () => {
+ it("should visit the users page", () => {
+ loginBeforeEach("admin_user", "123456");
+ cy.visit("/users");
+ cy.get("#djHideToolBarButton").click();
+ cy.contains('div', 'My profile').click();
+ cy.contains('a', 'Edit Profile').click();
+ cy.get('#form-field-givenName').clear()
+ cy.get('[data-cy="first name"]').type("New first name");
+ cy.get('#form-field-familyName').clear()
+ cy.get('[data-cy="last name"]').type("New last name");
+ cy.get('[data-cy="submit"]').click();
+ assert(cy.get("h4").contains("New first name New last name"));
+
+ });
+ });
\ No newline at end of file
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
index fa61ddfdf..f70c54e0b 100644
--- a/e2e/package-lock.json
+++ b/e2e/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.1",
"devDependencies": {
"cypress": "^13.6.2",
+ "cypress-file-upload": "^5.0.8",
"typescript": "~4.8.4"
},
"engines": {
@@ -609,6 +610,18 @@
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
}
},
+ "node_modules/cypress-file-upload": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz",
+ "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.2.1"
+ },
+ "peerDependencies": {
+ "cypress": ">3.0.0"
+ }
+ },
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
diff --git a/e2e/package.json b/e2e/package.json
index 4709473de..8b3ce8ea6 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -4,11 +4,11 @@
"description": "CoMSES Net e2e/integration tests",
"main": "index.ts",
"scripts": {
- "cy:run": "cypress run",
- "test": "./wait-for-it.sh server:8000 -t 120 -- cypress run"
+ "test": "cypress run"
},
"devDependencies": {
"cypress": "^13.6.2",
+ "cypress-file-upload": "^5.0.8",
"typescript": "~4.8.4"
},
"engines": {
diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts
new file mode 100644
index 000000000..8959a4f25
--- /dev/null
+++ b/frontend/cypress.config.ts
@@ -0,0 +1,7 @@
+export default {
+ e2e: {
+ setupNodeEvents(on, config) {
+ // implement node event listeners here
+ },
+ },
+};
diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json
new file mode 100644
index 000000000..02e425437
--- /dev/null
+++ b/frontend/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts
new file mode 100644
index 000000000..698b01a42
--- /dev/null
+++ b/frontend/cypress/support/commands.ts
@@ -0,0 +1,37 @@
+///
+// ***********************************************
+// This example commands.ts 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) => { ... })
+//
+// declare global {
+// namespace Cypress {
+// interface Chainable {
+// login(email: string, password: string): Chainable
+// drag(subject: string, options?: Partial): Chainable
+// dismiss(subject: string, options?: Partial): Chainable
+// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
+// }
+// }
+// }
\ No newline at end of file
diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts
new file mode 100644
index 000000000..f80f74f8e
--- /dev/null
+++ b/frontend/cypress/support/e2e.ts
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.ts 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'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
\ No newline at end of file
diff --git a/frontend/src/components/CodebaseEditForm.vue b/frontend/src/components/CodebaseEditForm.vue
index e4605a0ab..b617a8918 100644
--- a/frontend/src/components/CodebaseEditForm.vue
+++ b/frontend/src/components/CodebaseEditForm.vue
@@ -5,6 +5,7 @@
name="title"
label="Title"
help="A short title describing this computational model, limited to 300 characters"
+ data-cy="codebase title"
required
/>
@@ -13,6 +14,7 @@
name="description"
label="Description"
help="A summary description of your model similar to an abstract. There is no limit on length but it should be kept as succinct as possible."
+ data-cy="codebase description"
required
/>
-