Merge pull request #816 from pateljannat/ui-tests

test: course creation flow
This commit is contained in:
Jannat Patel
2024-05-16 11:46:29 +05:30
committed by GitHub
11 changed files with 157 additions and 138 deletions

View File

@@ -4,45 +4,30 @@ on:
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ main, develop ] branches: [ main ]
jobs: jobs:
commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 200
- uses: actions/setup-node@v4
with:
node-version: 18
check-latest: true
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
linters: linters:
name: Semgrep Rules name: Semantic Commits
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-python@v5
- name: Set up Python
uses: actions/setup-python@v4
with: with:
python-version: '3.10' python-version: '3.10'
cache: pip
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules - name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules - name: Run Semgrep rules
run: | run: semgrep ci --config ./frappe-semgrep-rules/rules
pip install semgrep
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

View File

@@ -1,133 +1,150 @@
describe("Course Creation", () => { describe("Course Creation", () => {
it("creates a new course", () => { it("creates a new course", () => {
cy.login(); cy.login();
cy.visit("/courses"); cy.wait(1000);
cy.visit("/lms/courses");
// Create a course // Create a course
cy.get("a.btn").contains("Create a Course").click(); cy.get("a").contains("New Course").click();
cy.wait(1000);
cy.url().should("include", "/courses/new-course/edit");
cy.get("#title").type("Test Course");
cy.get("#intro").type("Test Course Short Introduction");
cy.get("#description").type("Test Course Description");
cy.get("#video-link").type("-LPmw2Znl2c");
cy.get("#tags-input").type("Test");
cy.get("#published").check();
cy.wait(1000); cy.wait(1000);
cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course");
cy.get("label")
.contains("Short Introduction")
.type("Test Course Short Introduction to test the UI");
cy.get("div[contenteditable=true").invoke(
"text",
"Test Course Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
);
cy.fixture("profile.png", "base64").then((fileContent) => {
cy.get('input[type="file"]').attachFile({
fileContent,
fileName: "profile.png",
mimeType: "image/png",
encoding: "base64",
});
});
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label").contains("Published").click();
cy.get("label").contains("Published On").type("2021-01-01");
cy.button("Save").click(); cy.button("Save").click();
// Add Chapter // Add Chapter
cy.wait(1000); cy.wait(1000);
cy.link("Course Outline").click(); cy.button("Add Chapter").click();
cy.wait(1000); cy.wait(1000);
cy.get(".edit-header .btn-add-chapter").click(); cy.get("[id^=headlessui-dialog-panel-")
cy.wait(500); .should("be.visible")
cy.get("#chapter-title").type("Test Chapter"); .within(() => {
cy.get("#chapter-description").type("Test Chapter Description"); cy.get("label").contains("Title").type("Test Chapter");
cy.button("Save").click(); cy.button("Add Chapter").click();
});
// Add Lesson // Add Lesson
cy.wait(1000); cy.wait(1000);
cy.link("Add Lesson").click(); cy.button("Add Lesson").click();
cy.wait(1000);
cy.url().should("include", "/learn/1-1/edit");
cy.wait(1000); cy.wait(1000);
cy.get("#lesson-title").type("Test Lesson");
// Content cy.get("label").contains("Title").type("Test Lesson");
cy.get(".collapse-section.collapsed:first").click(); /* cy.get("#content .ce-block")
cy.get("#lesson-content .ce-block")
.click() .click()
.type( .invoke("text", "https://www.youtube.com/watch?v=GoDtyItReto"); */
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now. {enter}" /* cy.get("#content .ce-block")
); .click()
cy.get("#lesson-content .ce-toolbar__plus").click(); .paste("https://www.youtube.com/watch?v=GoDtyItReto"); */
cy.get('#lesson-content [data-item-name="youtube"]').click();
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto"); cy.fixture("Youtube.mov", "base64").then((fileContent) => {
cy.button("Insert").click(); cy.get('input[type="file"]').attachFile({
cy.wait(1000); fileContent,
fileName: "Youtube.mov",
mimeType: "image/png",
encoding: "base64",
});
});
cy.get("#content .ce-block").type(
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
);
cy.button("Save").click(); cy.button("Save").click();
// View Course // View Course
cy.wait(1000); cy.wait(1000);
cy.visit("/courses"); cy.visit("/lms");
cy.get(".course-card-title:first").contains("Test Course"); cy.wait(500);
cy.get(".course-card:first").click(); cy.url().should("include", "/lms/courses");
cy.url().should("include", "/courses/test-course"); cy.get(".grid a:first").within(() => {
cy.get("#title").contains("Test Course"); cy.get("div").contains("Test Course");
cy.get(".preview-video").should( cy.get("div").contains(
"Test Course Short Introduction to test the UI"
);
cy.get(".course-image")
.invoke("css", "background-image")
.should("include", "/files/profile");
});
cy.get(".grid a:first").click();
cy.url().should("include", "/lms/courses/test-course");
cy.get("div").contains("Test Course");
cy.get("div").contains("Test Course Short Introduction to test the UI");
cy.get("div").contains("Learning");
cy.get("div").contains("Frappe");
cy.get("div").contains("ERPNext");
cy.get("iframe").should(
"have.attr", "have.attr",
"src", "src",
"https://www.youtube.com/embed/-LPmw2Znl2c" "https://www.youtube.com/embed/-LPmw2Znl2c"
); );
cy.get("#intro").contains("Test Course Short Introduction");
// View Chapter // View Chapter
cy.get(".chapter-title-main:first").contains("Test Chapter"); cy.get("div").contains("Test Chapter");
cy.get(".chapter-description:first").contains( cy.get("[id^=headlessui-disclosure-panel-").within(() => {
"Test Chapter Description" cy.get("div").contains("Test Lesson").click();
); });
cy.get(".lesson-info:first").contains("Test Lesson"); cy.wait(1000);
cy.get(".lesson-info:first").click();
// View Lesson // View Lesson
cy.wait(1000); cy.url().should("include", "/learn/1-1");
cy.url().should("include", "learn/1.1"); cy.get("div").contains("Test Lesson");
cy.get("#title").contains("Test Lesson"); cy.get("div").contains(
cy.get(".lesson-video iframe").should( "This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now. "
"have.attr",
"src",
"https://www.youtube.com/embed/GoDtyItReto"
);
cy.get(".lesson-content-card").contains(
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
); );
cy.get("video")
.should("be.visible")
.children("source")
.invoke("attr", "src")
.should("include", "/files/Youtube");
// Add Discussion // Add Discussion
cy.get(".reply").click(); cy.button("New Question").click();
cy.wait(500); cy.wait(500);
cy.get(".discussion-modal").should("be.visible"); cy.get("[id^=headlessui-dialog-panel-").within(() => {
cy.get("label").contains("Title").type("Test Discussion");
// Enter title cy.get("div[contenteditable=true]").invoke(
cy.get(".modal .topic-title") "text",
.type("Discussion from tests") "This is a test discussion. This will check if the UI is working properly."
.should("have.value", "Discussion from tests");
// Enter comment
cy.get(".modal .discussions-comment").type(
"This is a discussion from the cypress ui tests."
);
// Submit
cy.get(".modal .submit-discussion").click();
cy.wait(2000);
// Check if discussion is added to page and content is visible
cy.get(".sidebar-parent:first .discussion-topic-title").should(
"have.text",
"Discussion from tests"
);
cy.get(".sidebar-parent:first .discussion-topic-title").click();
cy.get(".discussion-on-page:visible").should("have.class", "show");
cy.get(
".discussion-on-page:visible .reply-card .reply-text .ql-editor p"
).should(
"have.text",
"This is a discussion from the cypress ui tests."
);
cy.get(".discussion-form:visible .discussions-comment").type(
"This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
);
cy.get(".discussion-form:visible .submit-discussion").click();
cy.wait(3000);
cy.get(".discussion-on-page:visible").should("have.class", "show");
cy.get(".discussion-on-page:visible")
.children(".reply-card")
.eq(1)
.find(".reply-text")
.should(
"have.text",
"This is a discussion from the cypress ui tests. This comment was entered through the commentbox on the page.\n"
); );
cy.button("Post").click();
});
// View Discussion
cy.wait(500);
cy.get("div").contains("Test Discussion").click();
cy.get("div[contenteditable=true").invoke(
"text",
"This is a test comment. This will check if the UI is working properly."
);
cy.get("div").contains(
"This is a test comment. This will check if the UI is working properly."
);
}); });
}); });

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -24,6 +24,8 @@
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import "cypress-file-upload";
Cypress.Commands.add("login", (email, password) => { Cypress.Commands.add("login", (email, password) => {
if (!email) { if (!email) {
email = Cypress.config("testUser") || "Administrator"; email = Cypress.config("testUser") || "Administrator";
@@ -53,3 +55,13 @@ Cypress.Commands.add("iconButton", (text) => {
Cypress.Commands.add("dialog", (selector) => { Cypress.Commands.add("dialog", (selector) => {
return cy.get(`[role=dialog] ${selector}`); return cy.get(`[role=dialog] ${selector}`);
}); });
Cypress.Commands.add("paste", { prevSubject: true }, (subject, text) => {
cy.wrap(subject).then(($element) => {
const element = $element[0];
element.focus();
element.textContent = text;
const event = new Event("paste", { bubbles: true });
element.dispatchEvent(event);
});
});

View File

@@ -147,8 +147,8 @@ const props = defineProps({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background-color: theme('colors.orange.100'); background-color: theme('colors.green.100');
color: theme('colors.orange.600'); color: theme('colors.green.600');
} }
.avatar-group { .avatar-group {

View File

@@ -48,7 +48,7 @@
{{ {{
uploading uploading
? __('Uploading {0}%').format(progress) ? __('Uploading {0}%').format(progress)
: __('Upload an File') : __('Upload a File')
}} }}
</Button> </Button>
</div> </div>

View File

@@ -5,7 +5,7 @@
size: '2xl', size: '2xl',
actions: [ actions: [
{ {
label: 'Submit', label: 'Post',
variant: 'solid', variant: 'solid',
onClick: (close) => submitTopic(close), onClick: (close) => submitTopic(close),
}, },
@@ -15,10 +15,7 @@
<template #body-content> <template #body-content>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<div class="mb-1.5 text-sm text-gray-600"> <FormControl v-model="topic.title" :label="__('Title')" type="text" />
{{ __('Title') }}
</div>
<Input type="text" v-model="topic.title" />
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600"> <div class="mb-1.5 text-sm text-gray-600">
@@ -37,7 +34,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui' import { Dialog, FormControl, TextEditor, createResource } from 'frappe-ui'
import { reactive, defineModel, computed } from 'vue' import { reactive, defineModel, computed } from 'vue'
import { showToast } from '@/utils' import { showToast } from '@/utils'

View File

@@ -114,7 +114,11 @@
@click="removeTag(tag)" @click="removeTag(tag)"
/> />
</div> </div>
<FormControl v-model="newTag" @keyup.enter="updateTags()" /> <FormControl
v-model="newTag"
@keyup.enter="updateTags()"
id="tags"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@
<meta name="twitter:title" content="{{ meta.title }}" /> <meta name="twitter:title" content="{{ meta.title }}" />
<meta name="twitter:image" content="{{ meta.image }}" /> <meta name="twitter:image" content="{{ meta.image }}" />
<meta name="twitter:description" content="{{ meta.description }}" /> <meta name="twitter:description" content="{{ meta.description }}" />
<script type="module" crossorigin src="/assets/lms/frontend/assets/index-THAMiyCA.js"></script> <script type="module" crossorigin src="/assets/lms/frontend/assets/index-CdhjdjEj.js"></script>
<link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-CgFK8870.js"> <link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-CgFK8870.js">
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css"> <link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css">
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-C1pDkvO9.css"> <link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-C1pDkvO9.css">

View File

@@ -22,5 +22,9 @@
"bugs": { "bugs": {
"url": "https://github.com/frappe/lms/issues" "url": "https://github.com/frappe/lms/issues"
}, },
"homepage": "https://github.com/frappe/lms#readme" "homepage": "https://github.com/frappe/lms#readme",
"devDependencies": {
"cypress": "^13.9.0",
"cypress-file-upload": "^5.0.8"
}
} }