diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18490980..630341d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Run tests +name: Server Tests on: push: branches: diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 00000000..b70888ad --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,116 @@ +name: UI + +on: + pull_request: + workflow_dispatch: + push: + branches: [ main ] + +permissions: + # Do not change this as GITHUB_TOKEN is being used by roulette + contents: read + +jobs: + + test: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'frappe' }} + timeout-minutes: 60 + + strategy: + fail-fast: false + + name: UI Tests (Cypress) + + services: + mariadb: + image: mariadb:10.6 + env: + MARIADB_ROOT_PASSWORD: travis + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Check for valid Python & Merge Conflicts + run: | + python -m compileall -q -f "${GITHUB_WORKSPACE}" + if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}" + then echo "Found merge conflicts" + exit 1 + fi + + - uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + + - name: Add to Hosts + run: | + echo "127.0.0.1 lms.test" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-ui-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn-ui- + + - name: Cache cypress binary + uses: actions/cache@v3 + with: + path: ~/.cache/Cypress + key: ${{ runner.os }}-cypress + + - name: Install Dependencies + run: | + bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh + bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} + AFTER: ${{ env.GITHUB_EVENT_PATH.after }} + TYPE: ui + DB: mariadb + + - name: Site Setup + run: | + cd ~/frappe-bench/ + bench --site lms.test execute frappe.utils.install.complete_setup_wizard + bench --site lms.test execute frappe.tests.ui_test_helpers.create_test_user + + - name: UI Tests + run: cd ~/frappe-bench/ && bench --site lms.test run-ui-tests lms --headless + env: + CYPRESS_BASE_URL: http://lms.test:8000 + CYPRESS_RECORD_KEY: 095366ec-7b9f-41bd-aeec-03bb76d627fe + + - name: Stop server and wait for coverage file + run: | + ps -ef | grep "[f]rappe serve" | awk '{print $2}' | xargs kill -s SIGINT + sleep 5 + + - name: Show bench output + if: ${{ always() }} + run: cat ~/frappe-bench/bench_start.log || true \ No newline at end of file diff --git a/cypress.config.js b/cypress.config.js index 79d7508a..344cc769 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,12 +1,18 @@ const { defineConfig } = require("cypress"); module.exports = defineConfig({ - projectId: "", + projectId: "vandxn", adminPassword: "admin", testUser: "ash@ipp.com", + defaultCommandTimeout: 20000, + pageLoadTimeout: 15000, + video: true, + videoUploadOnPasses: false, retries: { runMode: 2, openMode: 0, }, - e2e: {}, + e2e: { + baseUrl: "http://test_site_ui:8000", + }, }); diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 8dcb0154..32c3fc49 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -5,10 +5,96 @@ describe("Course Creation", () => { }); it("creates a new course", () => { - cy.get("button").contains("Create a Course").click(); - cy.get("button").contains("Add Tag").click(); + // Create a course + cy.get("a.btn").contains("Create a Course").click(); + cy.wait(1000); + cy.url().should("include", "/courses/new-course"); + cy.button("Add Tag").click(); cy.get(".course-card-pills").type("Test"); cy.get("#title").type("Test Course"); cy.get("#intro").type("Test Course Short Introduction"); + cy.get("#video-link").type("-LPmw2Znl2c"); + cy.get("#published").check(); + cy.get("#description").type("Test Course Description"); + cy.wait(1000); + cy.button("Save Course Details").click(); + + // Add Cha + cy.wait(3000); + cy.button("New Chapter").click(); + cy.get(".new-chapter .chapter-title-main").type("Test Chapter"); + cy.get(".new-chapter .chapter-description").type( + "Test Chapter Description" + ); + cy.get(".new-chapter .btn-save-chapter").click(); + + // Add Lesson + cy.wait(3000); + cy.get(".chapter-parent .btn-lesson").click(); + + cy.wait(3000); + cy.get("#title").type("Test Lesson"); + cy.get("#youtube").type("GoDtyItReto"); + cy.get("#body").type("Test Lesson Content"); + cy.wait(1000); + cy.get(".btn-lesson").click(); + + // View Course + cy.wait(3000); + cy.visit("/courses"); + cy.get(".course-card-title:first").contains("Test Course"); + cy.get(".course-card:first").click(); + cy.url().should("include", "/courses/test-course"); + cy.get("#title").contains("Test Course"); + cy.get(".preview-video").should( + "have.attr", + "src", + "https://www.youtube.com/embed/-LPmw2Znl2c" + ); + cy.get("#intro").contains("Test Course Short Introduction"); + + // View Chapter + cy.get(".chapter-title-main:first").contains("Test Chapter"); + cy.get(".chapter-description:first").contains( + "Test Chapter Description" + ); + cy.get(".lesson-info:first").contains("Test Lesson"); + cy.get(".lesson-info:first").click(); + + // View Lesson + cy.wait(3000); + cy.url().should("include", "learn/1.1"); + cy.get("#title").contains("Test Lesson"); + cy.get(".lesson-video iframe").should( + "have.attr", + "src", + "https://www.youtube.com/embed/GoDtyItReto" + ); + cy.get(".lesson-content-card").contains("Test Lesson Content"); + + // Add Discussion + cy.get(".reply").click(); + cy.wait(500); + cy.get(".topic-title").type("Question Title"); + cy.get(".comment-field").type( + "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." + ); + cy.get(".submit-discussion").click(); + + // View Discussion + cy.wait(3000); + cy.get(".discussion-topic-title:first").contains("Question Title"); + cy.get(".sidebar-parent:first").click(); + cy.get(".reply-text").contains( + "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." + ); + cy.get(".comment-field:visible").type( + "This is a reply to the previous comment. Its not that long." + ); + cy.get(".submit-discussion:visible").click(); + cy.wait(1000); + cy.get(".reply-text:last p").contains( + "This is a reply to the previous comment. Its not that long." + ); }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 31945180..336a6355 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -42,6 +42,10 @@ Cypress.Commands.add("button", (text) => { return cy.get(`button:contains("${text}")`); }); +Cypress.Commands.add("link", (text) => { + return cy.get(`a:contains("${text}")`); +}); + Cypress.Commands.add("iconButton", (text) => { return cy.get(`button[aria-label="${text}"]`); }); diff --git a/lms/lms/doctype/lms_course/lms_course.json b/lms/lms/doctype/lms_course/lms_course.json index 94629b28..0b6a450c 100644 --- a/lms/lms/doctype/lms_course/lms_course.json +++ b/lms/lms/doctype/lms_course/lms_course.json @@ -53,7 +53,6 @@ "in_list_view": 1, "label": "Title", "reqd": 1, - "unique": 1, "width": "200" }, { @@ -261,7 +260,7 @@ } ], "make_attachments_public": 1, - "modified": "2023-02-23 09:45:54.826324", + "modified": "2023-02-23 09:45:54.826327", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course",