diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 7269fccd..fd9b940c 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -1,4 +1,4 @@ -name: UI +name: UI Tests on: pull_request: @@ -16,13 +16,14 @@ permissions: jobs: test: + name: UI Tests (Cypress) - ${{ matrix.containers }} runs-on: ubuntu-latest timeout-minutes: 60 strategy: fail-fast: false - - name: UI Tests (Cypress) + matrix: + containers: [1, 2] services: mariadb: @@ -114,6 +115,15 @@ jobs: env: CYPRESS_BASE_URL: http://lms.test:8000 CYPRESS_RECORD_KEY: 095366ec-7b9f-41bd-aeec-03bb76d627fe + SPLIT: ${{ strategy.job-total }} + SPLIT_INDEX: ${{ strategy.job-index }} + + - name: Upload Cypress screenshots if tests fail + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: cypress-screenshots-${{ matrix.containers }} + path: cypress/screenshots - name: Stop server and wait for coverage file run: | diff --git a/cypress.config.js b/cypress.config.js index b4ccfb3a..9557d567 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,4 +1,5 @@ import { defineConfig } from "cypress"; +import cypressSplit from "cypress-split"; export default defineConfig({ projectId: "vandxn", @@ -14,5 +15,12 @@ export default defineConfig({ }, e2e: { baseUrl: "http://pertest:8000", + setupNodeEvents(on, config) { + // Splitting tests only works when Cypress Cloud is not orchestrating parallel runs. + if (process.env.CYPRESS_CLOUD_PARALLEL !== "1") { + cypressSplit(on, config); + } + return config; + }, }, }); diff --git a/frontend/src/components/Controls/Uploader.vue b/frontend/src/components/Controls/Uploader.vue index 1f6ec5ee..9762b81c 100644 --- a/frontend/src/components/Controls/Uploader.vue +++ b/frontend/src/components/Controls/Uploader.vue @@ -9,6 +9,7 @@ :fileTypes="[fileType]" :validateFile="(file: File) => validateFile(file, true, type)" @success="(file: File) => saveFile(file)" + @failure="onUploadFailure" > diff --git a/frontend/src/components/Modals/FeedbackModal.vue b/frontend/src/components/Modals/FeedbackModal.vue index b69d6615..abb97478 100644 --- a/frontend/src/components/Modals/FeedbackModal.vue +++ b/frontend/src/components/Modals/FeedbackModal.vue @@ -2,7 +2,7 @@ @@ -63,9 +70,11 @@ + diff --git a/frontend/src/components/Quiz.vue b/frontend/src/components/Quiz.vue index fd0e0c26..84e17442 100644 --- a/frontend/src/components/Quiz.vue +++ b/frontend/src/components/Quiz.vue @@ -228,14 +228,6 @@ :model-value="reviewQuestions.includes(activeQuestion) ? 1 : 0" @change="markForReview($event, activeQuestion)" /> -
@@ -315,7 +309,7 @@
{{ index }} diff --git a/frontend/src/pages/Batches/BatchForm.vue b/frontend/src/pages/Batches/BatchForm.vue index aa71bedb..a6440619 100644 --- a/frontend/src/pages/Batches/BatchForm.vue +++ b/frontend/src/pages/Batches/BatchForm.vue @@ -328,8 +328,7 @@ import { updateMetaInfo, } from '@/utils' import { useRouter } from 'vue-router' -import { useOnboarding, useTelemetry } from 'frappe-ui/frappe' -import { sessionStore } from '@/stores/session' +import { useTelemetry } from 'frappe-ui/frappe' import Uploader from '@/components/Controls/Uploader.vue' import MultiSelect from '@/components/Controls/MultiSelect.vue' import Link from '@/components/Controls/Link.vue' @@ -340,8 +339,6 @@ import EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue' const router = useRouter() const user = inject('$user') -const { brand } = sessionStore() -const { updateOnboardingStep } = useOnboarding('learning') const instructors = ref([]) const app = getCurrentInstance() const { capture } = useTelemetry() diff --git a/frontend/src/pages/Batches/Batches.vue b/frontend/src/pages/Batches/Batches.vue index e48db2fe..f46571a0 100644 --- a/frontend/src/pages/Batches/Batches.vue +++ b/frontend/src/pages/Batches/Batches.vue @@ -78,13 +78,14 @@
- + + +
{ return user.data?.is_moderator || user.data?.is_evaluator }) - diff --git a/frontend/src/pages/Batches/components/BatchOverlay.vue b/frontend/src/pages/Batches/components/BatchOverlay.vue index 73777cee..e71b0b13 100644 --- a/frontend/src/pages/Batches/components/BatchOverlay.vue +++ b/frontend/src/pages/Batches/components/BatchOverlay.vue @@ -1,100 +1,108 @@ diff --git a/frontend/src/pages/Courses/CourseForm.vue b/frontend/src/pages/Courses/CourseForm.vue index ad84387a..c32dd3a0 100644 --- a/frontend/src/pages/Courses/CourseForm.vue +++ b/frontend/src/pages/Courses/CourseForm.vue @@ -45,30 +45,35 @@ @update:modelValue="makeFormDirty()" />
-
+
- -
-
-
- {{ tag }} - -
-
+ +
+ +
@@ -355,11 +360,9 @@ import { sanitizeHTML, updateMetaInfo, createLMSCategory, - cleanError, } from '@/utils' -import { Trash2, X } from 'lucide-vue-next' +import { X } from 'lucide-vue-next' import { useRouter } from 'vue-router' -import { sessionStore } from '../../stores/session' import Link from '@/components/Controls/Link.vue' import CourseOutline from '@/components/CourseOutline.vue' import MultiSelect from '@/components/Controls/MultiSelect.vue' @@ -369,7 +372,6 @@ import NewMemberModal from '@/components/Modals/NewMemberModal.vue' const user = inject('$user') const newTag = ref('') -const { brand } = sessionStore() const router = useRouter() const instructors = ref([]) const related_courses = ref([]) @@ -412,6 +414,11 @@ const courseResource = createDocumentResource({ auto: true, }) +const parsedTags = computed(() => { + const tags = courseResource.doc?.tags + return tags ? tags.split(', ').filter(Boolean) : [] +}) + watch( () => courseResource.doc, () => { @@ -595,13 +602,6 @@ const makeFormDirty = () => { isDirty.value = true } -usePageMeta(() => { - return { - title: courseResource.doc?.title, - icon: brand.favicon, - } -}) - defineExpose({ submitCourse, trashCourse, diff --git a/frontend/src/pages/JobDetail.vue b/frontend/src/pages/JobDetail.vue index d8f80d18..5735f890 100644 --- a/frontend/src/pages/JobDetail.vue +++ b/frontend/src/pages/JobDetail.vue @@ -157,7 +157,7 @@ import { createResource, usePageMeta, } from 'frappe-ui' -import { inject, ref, computed } from 'vue' +import { inject, ref, computed, watch, nextTick } from 'vue' import { sessionStore } from '../stores/session' import JobApplicationModal from '@/components/Modals/JobApplicationModal.vue' import { @@ -193,17 +193,11 @@ const job = createResource({ }, cache: ['job', props.job], auto: true, - onSuccess: (data) => { - if (user.data?.name) { - jobApplication.submit() - applicationCount.submit() - } - }, }) const jobApplication = createResource({ url: 'frappe.client.get_list', - makeParams(values) { + makeParams() { return { doctype: 'LMS Job Application', filters: { @@ -216,7 +210,7 @@ const jobApplication = createResource({ const applicationCount = createResource({ url: 'frappe.client.get_count', - makeParams(values) { + makeParams() { return { doctype: 'LMS Job Application', filters: { @@ -226,6 +220,18 @@ const applicationCount = createResource({ }, }) +const stopWatch = watch( + () => [job.data?.name, user.data?.name], + ([jobName, userName]) => { + if (jobName && userName) { + jobApplication.submit() + applicationCount.submit() + nextTick(() => stopWatch()) + } + }, + { immediate: true } +) + const openApplicationModal = () => { showApplicationModal.value = true } diff --git a/frontend/src/pages/ProfileEvaluator.vue b/frontend/src/pages/ProfileEvaluator.vue index c34e4fb9..78ef4db7 100644 --- a/frontend/src/pages/ProfileEvaluator.vue +++ b/frontend/src/pages/ProfileEvaluator.vue @@ -151,7 +151,7 @@