diff --git a/.github/workflows/pull_request_cypress.yml b/.github/workflows/pull_request_cypress.yml new file mode 100644 index 00000000..1bbcc428 --- /dev/null +++ b/.github/workflows/pull_request_cypress.yml @@ -0,0 +1,98 @@ +name: Pull Request Cypress Testing + +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: [master, main] + +env: + PAGE_LIMIT: ${{ github.event.inputs.PAGE_LIMIT }} + SEARCH_KEYWORDS: ${{ github.event.inputs.SEARCH_KEYWORDS }} + FILTER_FACET: ${{ github.event.inputs.FILTER_FACET }} + MULTIPLE_FILTER_FACETS: ${{ github.event.inputs.MULTIPLE_FILTER_FACETS }} + DATASET_IDS: ${{ github.event.inputs.DATASET_IDS }} + TAXON_MODELS: ${{ github.event.inputs.TAXON_MODELS }} + THREE_SYNC_VIEW: ${{ github.event.inputs.THREE_SYNC_VIEW }} + SEARCH_IN_MAP: ${{ github.event.inputs.SEARCH_IN_MAP }} + SCAFFOLD_DATASET_IDS: ${{ github.event.inputs.SCAFFOLD_DATASET_IDS }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }} + AWS_USER_POOL_ID: ${{ secrets.AWS_USER_POOL_ID }} + AWS_USER_POOL_WEB_CLIENT_ID: ${{ secrets.AWS_USER_POOL_WEB_CLIENT_ID }} + BIOLUCIDA_PASSWORD: ${{ secrets.BIOLUCIDA_PASSWORD }} + BIOLUCIDA_USERNAME: ${{ secrets.BIOLUCIDA_USERNAME }} + BITLY_ACCESS_TOKEN: ${{ secrets.BITLY_ACCESS_TOKEN }} + BLACKFYNN_API_SECRET: ${{ secrets.BLACKFYNN_API_SECRET }} + BLACKFYNN_API_TOKEN: ${{ secrets.BLACKFYNN_API_TOKEN }} + BLACKFYNN_CONCEPTS_API_HOST: ${{ secrets.BLACKFYNN_CONCEPTS_API_HOST }} + CTF_API_HOST: ${{ secrets.CTF_API_HOST }} + CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} + CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} + DEPLOY_ENV: ${{ secrets.DEPLOY_ENV }} + DISABLE_REDIRECT_SSL: ${{ secrets.DISABLE_REDIRECT_SSL }} + FLATMAP_API_HOST: ${{ secrets.FLATMAP_API_HOST }} + LOGIN_API_URL: ${{ secrets.LOGIN_API_URL }} + NODE_ENV: ${{ secrets.NODE_ENV }} + NPM_CONFIG_PRODUCTION: ${{ secrets.NPM_CONFIG_PRODUCTION }} + OSPARC_HOST: ${{ secrets.OSPARC_HOST }} + PORTAL_API_HOST: ${{ secrets.PORTAL_API_HOST }} + RECAPTCHA_SECRET_KEY: ${{ secrets.RECAPTCHA_SECRET_KEY }} + RECAPTCHA_SITE_KEY: ${{ secrets.RECAPTCHA_SITE_KEY }} + SHOW_FUNDING_FACET: ${{ secrets.SHOW_FUNDING_FACET }} + SHOW_HIERARCHAL_FACETS: ${{ secrets.SHOW_HIERARCHAL_FACETS }} + SHOW_LOGIN_FEATURE: ${{ secrets.SHOW_LOGIN_FEATURE }} + SHOW_METRICS: ${{ secrets.SHOW_METRICS }} + SHOW_OSPARC_TAB: ${{ secrets.SHOW_OSPARC_TAB }} + SHOW_TIMESERIES_VIEWER: ${{ secrets.SHOW_TIMESERIES_VIEWER }} + SPARC_PORTAL_USER_ID: ${{ secrets.SPARC_PORTAL_USER_ID }} + SPARC_PORTAL_USER_SECRET: ${{ secrets.SPARC_PORTAL_USER_SECRET }} + +jobs: + pull-request-cypress-run: + if: github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + containers: [1, 2, 3, 4] + + steps: + - uses: actions-cool/check-user-permission@main + id: checkUser + with: + require: "write" + username: ${{ github.event.pull_request.user.name }} + + - name: Checkout + if: steps.checkUser.outputs.require-result == 'true' + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.17.1 + + - name: Automated Run Testing when Pull Request + if: steps.checkUser.outputs.require-result == 'true' + uses: cypress-io/github-action@v6 + env: + ROOT_URL: ${{ secrets.ROOT_URL }} # http://localhost:3000 + with: + build: yarn build + start: yarn preview + wait-on: ${{ secrets.ROOT_URL }} + record: true + parallel: true + + - name: Skip tests + if: steps.checkUser.outputs.require-result == 'false' + uses: actions/github-script@v4 + with: + script: | + core.setFailed('Pull request is opened by an user without write permission, tests are skipped for security reason') diff --git a/.github/workflows/cypress_timeline.yml b/.github/workflows/quality_control_cypress.yml similarity index 63% rename from .github/workflows/cypress_timeline.yml rename to .github/workflows/quality_control_cypress.yml index 039a459b..4b6d1005 100644 --- a/.github/workflows/cypress_timeline.yml +++ b/.github/workflows/quality_control_cypress.yml @@ -1,18 +1,6 @@ -# This is a basic workflow to help you get started with Actions - -name: Cypress Timeline Tests +name: Quality Control Cypress Testing on: - # push: - # branches: [master, main] - - pull_request_target: - types: [opened, synchronize, reopened] - branches: [master, main] - - schedule: - - cron: "0 0 * * 1" - workflow_dispatch: inputs: PORTAL_TARGET: @@ -113,107 +101,7 @@ env: SPARC_PORTAL_USER_SECRET: ${{ secrets.SPARC_PORTAL_USER_SECRET }} jobs: - pull-request-cypress-run: - if: github.event_name == 'pull_request_target' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - containers: [1, 2, 3, 4] - - steps: - - uses: actions-cool/check-user-permission@main - id: checkUser - with: - require: "write" - username: ${{ github.event.pull_request.user.name }} - - - name: Checkout - if: steps.checkUser.outputs.require-result == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 18.17.1 - - - name: Automated Run Testing when Pull Request - if: steps.checkUser.outputs.require-result == 'true' - uses: cypress-io/github-action@v6 - env: - ROOT_URL: ${{ secrets.ROOT_URL }} # http://localhost:3000 - with: - build: yarn build - start: yarn preview - wait-on: ${{ secrets.ROOT_URL }} - record: true - parallel: true - - - name: Skip tests - if: steps.checkUser.outputs.require-result == 'false' - uses: actions/github-script@v4 - with: - script: | - core.setFailed('Pull request is opened by an user without write permission, tests are skipped for security reason') - - schedule-cypress-run-staging: - if: github.event_name == 'schedule' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - containers: [1, 2, 3, 4] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 18.17.1 - - - name: Schedule Run Testing against Staging - uses: cypress-io/github-action@v6 - env: - ROOT_URL: "https://staging.sparc.science" - with: - wait-on: ${{ env.ROOT_URL }} - record: true - parallel: true - - schedule-cypress-run-production: - if: github.event_name == 'schedule' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - containers: [1, 2, 3, 4] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 18.17.1 - - - name: Schedule Run Testing against Production - uses: cypress-io/github-action@v6 - env: - ROOT_URL: "https://sparc.science" - with: - wait-on: ${{ env.ROOT_URL }} - record: true - parallel: true - - manual-cypress-run: + quality-control-cypress-run: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest diff --git a/.github/workflows/scheduled_cypress.yml b/.github/workflows/scheduled_cypress.yml new file mode 100644 index 00000000..f1f33988 --- /dev/null +++ b/.github/workflows/scheduled_cypress.yml @@ -0,0 +1,105 @@ +name: Scheduled Cypress Testing + +on: + schedule: + - cron: "0 0 * * 1" + +env: + PAGE_LIMIT: ${{ github.event.inputs.PAGE_LIMIT }} + SEARCH_KEYWORDS: ${{ github.event.inputs.SEARCH_KEYWORDS }} + FILTER_FACET: ${{ github.event.inputs.FILTER_FACET }} + MULTIPLE_FILTER_FACETS: ${{ github.event.inputs.MULTIPLE_FILTER_FACETS }} + DATASET_IDS: ${{ github.event.inputs.DATASET_IDS }} + TAXON_MODELS: ${{ github.event.inputs.TAXON_MODELS }} + THREE_SYNC_VIEW: ${{ github.event.inputs.THREE_SYNC_VIEW }} + SEARCH_IN_MAP: ${{ github.event.inputs.SEARCH_IN_MAP }} + SCAFFOLD_DATASET_IDS: ${{ github.event.inputs.SCAFFOLD_DATASET_IDS }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }} + AWS_USER_POOL_ID: ${{ secrets.AWS_USER_POOL_ID }} + AWS_USER_POOL_WEB_CLIENT_ID: ${{ secrets.AWS_USER_POOL_WEB_CLIENT_ID }} + BIOLUCIDA_PASSWORD: ${{ secrets.BIOLUCIDA_PASSWORD }} + BIOLUCIDA_USERNAME: ${{ secrets.BIOLUCIDA_USERNAME }} + BITLY_ACCESS_TOKEN: ${{ secrets.BITLY_ACCESS_TOKEN }} + BLACKFYNN_API_SECRET: ${{ secrets.BLACKFYNN_API_SECRET }} + BLACKFYNN_API_TOKEN: ${{ secrets.BLACKFYNN_API_TOKEN }} + BLACKFYNN_CONCEPTS_API_HOST: ${{ secrets.BLACKFYNN_CONCEPTS_API_HOST }} + CTF_API_HOST: ${{ secrets.CTF_API_HOST }} + CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} + CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} + DEPLOY_ENV: ${{ secrets.DEPLOY_ENV }} + DISABLE_REDIRECT_SSL: ${{ secrets.DISABLE_REDIRECT_SSL }} + FLATMAP_API_HOST: ${{ secrets.FLATMAP_API_HOST }} + LOGIN_API_URL: ${{ secrets.LOGIN_API_URL }} + NODE_ENV: ${{ secrets.NODE_ENV }} + NPM_CONFIG_PRODUCTION: ${{ secrets.NPM_CONFIG_PRODUCTION }} + OSPARC_HOST: ${{ secrets.OSPARC_HOST }} + PORTAL_API_HOST: ${{ secrets.PORTAL_API_HOST }} + RECAPTCHA_SECRET_KEY: ${{ secrets.RECAPTCHA_SECRET_KEY }} + RECAPTCHA_SITE_KEY: ${{ secrets.RECAPTCHA_SITE_KEY }} + SHOW_FUNDING_FACET: ${{ secrets.SHOW_FUNDING_FACET }} + SHOW_HIERARCHAL_FACETS: ${{ secrets.SHOW_HIERARCHAL_FACETS }} + SHOW_LOGIN_FEATURE: ${{ secrets.SHOW_LOGIN_FEATURE }} + SHOW_METRICS: ${{ secrets.SHOW_METRICS }} + SHOW_OSPARC_TAB: ${{ secrets.SHOW_OSPARC_TAB }} + SHOW_TIMESERIES_VIEWER: ${{ secrets.SHOW_TIMESERIES_VIEWER }} + SPARC_PORTAL_USER_ID: ${{ secrets.SPARC_PORTAL_USER_ID }} + SPARC_PORTAL_USER_SECRET: ${{ secrets.SPARC_PORTAL_USER_SECRET }} + +jobs: + scheduled-cypress-run-staging: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + containers: [1, 2, 3, 4] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.17.1 + + - name: Schedule Run Testing against Staging + uses: cypress-io/github-action@v6 + env: + ROOT_URL: "https://staging.sparc.science" + with: + wait-on: ${{ env.ROOT_URL }} + record: true + parallel: true + + scheduled-cypress-run-production: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + containers: [1, 2, 3, 4] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.17.1 + + - name: Schedule Run Testing against Production + uses: cypress-io/github-action@v6 + env: + ROOT_URL: "https://sparc.science" + with: + wait-on: ${{ env.ROOT_URL }} + record: true + parallel: true diff --git a/cypress.config.js b/cypress.config.js index 4d30a041..cda75d02 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,4 +1,5 @@ import { defineConfig } from "cypress"; +import dynamicConfig from './tests/cypress/support/dynamicConfig.js' export default defineConfig({ defaultCommandTimeout: 5000, @@ -17,9 +18,10 @@ export default defineConfig({ testIsolation: true, // pageLoadTimeout: 1024*1024*1024, setupNodeEvents(on, config) { - // implement node event listeners here + return dynamicConfig(config) }, env: { + PORTAL_API: process.env.PORTAL_API_HOST ? process.env.PORTAL_API_HOST : 'https://sparc-api.herokuapp.com', // databrowser.js PAGE_LIMIT: process.env.PAGE_LIMIT ? process.env.PAGE_LIMIT : '20', SEARCH_KEYWORDS: process.env.SEARCH_KEYWORDS ? process.env.SEARCH_KEYWORDS : 'Spine, Neck', diff --git a/package.json b/package.json index 2ad88bfd..b31ec4ba 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@nuxt/devtools": "latest", "@nuxtjs/turnstile": "^0.6.3", "@zadigetvoltaire/nuxt-gtm": "^0.0.13", - "cypress": "^13.6.6", + "cypress": "13.9.0", "nuxt": "^3.8.2", "nuxt-svgo": "^3.5.6", "sass": "^1.66.1", diff --git a/tests/cypress/e2e/databrowser.cy.js b/tests/cypress/e2e/databrowser.cy.js index fbcb839e..2f9362e4 100644 --- a/tests/cypress/e2e/databrowser.cy.js +++ b/tests/cypress/e2e/databrowser.cy.js @@ -35,13 +35,11 @@ browseCategories.forEach((category) => { beforeEach(function () { cy.intercept('**/query?**').as('query') - cy.intercept('**/entries?**').as('entries') }) it('Dataset card', function () { cy.wait('@query', { timeout: 20000 }) - cy.wait('@entries', { timeout: 20000 }) cy.get(':nth-child(1) > .el-table_1_column_1 > .cell > :nth-child(1) > .img-dataset > img').should('have.attr', 'src').and('contain', 'https://assets.discover.pennsieve.io/dataset-assets/') cy.get(':nth-child(1) > .el-table_1_column_2 > .cell > :nth-child(1) > .property-table > :nth-child(1) > .property-name-column').should('contain', 'Anatomical Structure'); @@ -135,12 +133,13 @@ browseCategories.forEach((category) => { // Click search button cy.get('.search-text').click() + cy.waitForLoadingMask() + // Check for keyword in URL cy.url().should('contain', keyword) cy.wait('@query', { timeout: 20000 }) - - cy.get('.table-wrap.el-loading-parent--relative > .el-loading-mask', { timeout: 30000 }).should('not.exist') + cy.waitForLoadingMask() // Check for result cy.get(':nth-child(1) > p').then(($result) => { @@ -189,104 +188,101 @@ browseCategories.forEach((category) => { if (!isExpanded) cy.wrap($ele).click() }) - // Check for filer + // Check for filter cy.get('.el-tree-node__content > .custom-tree-node > .capitalize:visible').then(($label) => { - let facetIsObserved = true - facetList.forEach((facet) => { - facetIsObserved = facetIsObserved && $label.text().toLowerCase().includes(facet.toLowerCase()) - }) - if (facetIsObserved) { + let facetsArray = [] + cy.wrap($label).each($span => facetsArray.push($span.text().toLowerCase())).then(() => { + let facetIsObserved = true facetList.forEach((facet) => { - // Check the matched facet checkbox - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() - - // Check for the number of facet tags in filters applied box - cy.get('.el-card__body > .capitalize:visible').contains(new RegExp(facet, 'i')).should('exist') + facetIsObserved = facetIsObserved && facetsArray.includes(facet.toLowerCase()) }) - - // Check for URL - cy.url().should('contain', 'selectedFacetIds') - - cy.wait('@query', { timeout: 20000 }) - - cy.get('.table-wrap.el-loading-parent--relative > .el-loading-mask', { timeout: 30000 }).should('not.exist') - - // Check for result correctness - cy.get(':nth-child(1) > p').then(($result) => { - if ($result.text().includes('0 Results | Showing')) { - // Empty text should exist if no result - cy.get('.el-table__empty-text').should('exist').and('have.text', 'No Results') - } else { - // Check for facets exist in dataset card - cy.get('.property-table').then(($content) => { - facetList.forEach((facet) => { - const facetExistInCard = $content.text().toLowerCase().includes(facet.toLowerCase()) - if (facetExistInCard) { - cy.wrap($content).contains(new RegExp(facet, 'i')).should('exist') - } else { - // *** Ignore when facets cannot be found or - // *** Find some other solutions in the future - } + if (facetIsObserved) { + facetList.forEach((facet) => { + // Check the matched facet checkbox + cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).then(($label) => { + cy.wrap($label).parent().siblings('.el-checkbox').then(($checkbox) => { + if (!$checkbox.hasClass('is-checked')) cy.wrap($label).click() }) }) - } - }) - for (let index = 0; index < 2; index++) { - if (index === 1) { - // Combine with search - cy.get('.el-input__inner').clear() - cy.get('.el-input__inner').type('dataset') - facetList.forEach((facet) => { - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() - }) - } - - // Uncheck - facetList.forEach((facet) => { - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() + // Check for the number of facet tags in filters applied box + cy.get('.el-card__body > .capitalize:visible').contains(new RegExp(facet, 'i')).should('exist') }) - cy.get('.el-card__body > .capitalize').should('not.exist') - cy.get('.no-facets').should('contain', 'No filters applied') - cy.url().should('not.contain', 'selectedFacetIds') - // Close all tags in order - facetList.forEach((facet) => { - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() - }) - facetList.forEach((facet) => { - // Close all facet tags in filters applied box - cy.get('.el-card__body > .capitalize').contains(new RegExp(`^${facet}$`, 'i')).siblings().click() + // Check for URL + cy.url().should('contain', 'selectedFacetIds') + + cy.wait('@query', { timeout: 20000 }) + cy.waitForLoadingMask() + + // Check for result correctness + cy.get(':nth-child(1) > p').then(($result) => { + if ($result.text().includes('0 Results | Showing')) { + // Empty text should exist if no result + cy.get('.el-table__empty-text').should('exist').and('have.text', 'No Results') + } else { + // Check for facets exist in dataset card + cy.get('.property-table').then(($content) => { + facetList.forEach((facet) => { + const facetExistInCard = $content.text().toLowerCase().includes(facet.toLowerCase()) + if (facetExistInCard) { + cy.wrap($content).contains(new RegExp(facet, 'i')).should('exist') + } else { + // *** Ignore when facets cannot be found or + // *** Find some other solutions in the future + } + }) + }) + } }) - cy.get('.el-card__body > .capitalize').should('not.exist') - cy.get('.no-facets').should('contain', 'No filters applied') - cy.url().should('not.contain', 'selectedFacetIds') - // Reset all - facetList.forEach((facet) => { - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() - }) - cy.get('.tags-container > .flex > .el-link > .el-link__inner').click() - cy.get('.el-card__body > .capitalize').should('not.exist') - cy.get('.no-facets').should('contain', 'No filters applied') - cy.url().should('not.contain', 'selectedFacetIds') + for (let index = 0; index < 2; index++) { + if (index === 1) { + // Combine with search + cy.get('.el-input__inner').clear() + cy.get('.el-input__inner').type('dataset') + cy.filterCheckbox(facetList, 'check', $label) + } - // Close a child facet tag and then click reset all - facetList.forEach((facet) => { - cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).click() - }) - cy.get('.el-card__body > .capitalize').then(($tag) => { - cy.get('.el-tag__close').last().click() + // Uncheck all + cy.filterCheckbox(facetList, 'uncheck', $label) + cy.checkFilterCleared() + + // Close all tags in order + cy.filterCheckbox(facetList, 'check', $label) + facetList.forEach((facet) => { + // Check the matched facet checkbox + cy.wrap($label).contains(new RegExp(`^${facet}$`, 'i')).then(($label) => { + cy.wrap($label).parent().siblings('.el-checkbox').then(($checkbox) => { + const isChecked = $checkbox.hasClass('is-checked') + const isIndeterminate = $checkbox.children().hasClass('is-indeterminate') + // Close all facet tags in filters applied box + if (isChecked) cy.get('.el-card__body > .capitalize').contains(new RegExp(`^${facet}$`, 'i')).siblings().click() + if (isIndeterminate) { + cy.get('.el-card__body > .capitalize > .el-tag__close').each(($close) => { + cy.wrap($close).click() + }) + } + }) + }) + }) + cy.checkFilterCleared() + + // Reset all + cy.filterCheckbox(facetList, 'check', $label) cy.get('.tags-container > .flex > .el-link > .el-link__inner').click() - cy.get('.el-card__body > .capitalize').should('not.exist') - cy.get('.no-facets').should('contain', 'No filters applied') - cy.url().should('not.contain', 'selectedFacetIds') - }) + cy.checkFilterCleared() + // Close one child facet tag and then click reset all + cy.filterCheckbox(facetList, 'check', $label) + cy.get('.el-card__body > .capitalize > .el-tag__close').last().click() + cy.get('.tags-container > .flex > .el-link > .el-link__inner').click() + cy.checkFilterCleared() + } + } else { + this.skip() } - } else { - this.skip() - } + }) }) }) }) diff --git a/tests/cypress/e2e/datasets.cy.js b/tests/cypress/e2e/datasets.cy.js index 9496c537..9dd87a01 100644 --- a/tests/cypress/e2e/datasets.cy.js +++ b/tests/cypress/e2e/datasets.cy.js @@ -92,28 +92,39 @@ datasetIds.forEach(datasetId => { cy.wrap($name).click() cy.wait('@query', { timeout: 20000 }) - cy.waitForLoadingMask() - // Check for result - cy.get(':nth-child(1) > p > .el-dropdown > .filter-dropdown').should('be.visible').click() - cy.get('.el-dropdown-menu > .el-dropdown-menu__item:visible').contains('View All').click() + cy.get('.table-wrap').then(($content) => { + if (!$content.text().includes('No Results')) { + // Check for result + cy.get(':nth-child(1) > p > .el-dropdown > .filter-dropdown').should('be.visible').click() + cy.get('.el-dropdown-menu > .el-dropdown-menu__item:visible').contains('View All').click() - let datasetShowUp = false - cy.get('.img-dataset > img').each(($img) => { - cy.wrap($img).invoke('attr', 'src').then((src) => { - datasetShowUp = datasetShowUp || src.includes(datasetId) - }) - }).then(() => { - if (!datasetShowUp) { - throw new Error("Can not find the dataset for current contributor") + cy.waitForLoadingMask() + + let datasetShowUp = false + cy.get('.img-dataset > img').each(($img) => { + cy.wrap($img).invoke('attr', 'src').then((src) => { + datasetShowUp = datasetShowUp || src.includes(datasetId) + }) + }).then(() => { + if (datasetShowUp) { + // Check for URL and search input + cy.url({ decode: true }).should('contain', `search=${$name.text().replaceAll(' ', '+')}`) + cy.get('.el-input__inner').should('have.value', $name.text()); + } else { + throw new Error("Can not find matched dataset for current contributor") + } + }) + } else { + // Skip test if can not find any datasets + this.skip() } - }) + cy.go('back') - // Check for URL and search input - cy.url({ decode: true }).should('contain', `search=${$name.text().replaceAll(' ', '+')}`) - cy.get('.el-input__inner').should('have.value', $name.text()); - cy.go('back') + cy.waitForLoadingMask() + + }) }); // Check 'View other version' directs to Versions tab @@ -133,14 +144,23 @@ datasetIds.forEach(datasetId => { }); it("Abstract Tab", function () { + cy.url().then((url) => { + if (!url.includes(`/datasets/${datasetId}?type=dataset`)) { + cy.go('back') + + cy.waitForLoadingMask() + + } + }) + // Should switch to 'Abstract' cy.get('#datasetDetailsTabsContainer > .style1', { timeout: 30000 }).contains('Abstract').click(); cy.get('.active.style1.tab2.tab-link.p-16').should('contain', 'Abstract'); //The following regular expression should capture space and letters - cy.get('.dataset-description-info > :nth-child(1)').contains(/Study Purpose: (.+)/).should('exist') - cy.get('.dataset-description-info > :nth-child(1)').contains(/Data (Collection|Collected):(.+)/).should('exist') - cy.get('.dataset-description-info > :nth-child(1)').contains(/(Primary )?Conclusion(s)?: (.+)/).should('exist') + cy.get('.dataset-description-info > :nth-child(1)').contains(/Study Purpose: (.+)/i).should('exist') + cy.get('.dataset-description-info > :nth-child(1)').contains(/Data (Collection|Collected):(.+)/i).should('exist') + cy.get('.dataset-description-info > :nth-child(1)').contains(/(Primary )?Conclusion(s)?: (.+)/i).should('exist') // Check for Experimental Design cy.get('.dataset-description-info > .mb-8').contains('Experimental Design:').should('exist') @@ -152,17 +172,17 @@ datasetIds.forEach(datasetId => { cy.get('.link2').should('have.attr', 'href').and('include', 'https://doi.org/') } }) - cy.get('.dataset-description-info > .experimental-design-container').contains(/Experimental Approach: (.+)/).should('exist') + cy.get('.dataset-description-info > .experimental-design-container').contains(/Experimental Approach: (.+)/i).should('exist') // Check for Subject Information cy.get('.dataset-description-info > .mb-8').contains('Subject Information:').should('exist') - cy.get('.dataset-description-info > .experimental-design-container').contains(/Anatomical structure: (.+)/).should('exist') - cy.get('.dataset-description-info > .experimental-design-container').contains(/Species: (.+)/).should('exist') - cy.get('.dataset-description-info > .experimental-design-container').contains(/Sex: (.+)/).should('exist') - cy.get('.dataset-description-info > .experimental-design-container').contains(/Number of samples: (.+)/).should('exist') + cy.get('.dataset-description-info > .experimental-design-container').contains(/Anatomical structure: (.+)/i).should('exist') + cy.get('.dataset-description-info > .experimental-design-container').contains(/Species: (.+)/i).should('exist') + cy.get('.dataset-description-info > .experimental-design-container').contains(/Sex: (.+)/i).should('exist') + cy.get('.dataset-description-info > .experimental-design-container').contains(/Number of samples: (.+)/i).should('exist') // Check for Keywords - cy.get('.dataset-description-info').contains(/Keywords: (.+)/).should('exist') + cy.get('.dataset-description-info').contains(/Keywords: (.+)/i).should('exist') // Check for downloading cy.contains('.dataset-description-info a', 'Download Metadata file').should('have.attr', 'href').and('include', 'metadata').then((href) => { @@ -178,19 +198,19 @@ datasetIds.forEach(datasetId => { cy.get('.active.style1.tab2.tab-link.p-16').should('contain', 'About'); // Check for content - cy.get('.dataset-about-info > .mb-16').contains(/Title: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/First Published: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Last Published: (.+)/).should('exist') - cy.get('.dataset-about-info > .about-section-container').contains(/Contact Author: (.+)/).within(($el) => { + cy.get('.dataset-about-info > .mb-16').contains(/Title: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/First Published: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Last Published: (.+)/i).should('exist') + cy.get('.dataset-about-info > .about-section-container').contains(/Contact Author: (.+)/i).within(($el) => { // Check for email link exist cy.wrap($el).get(':nth-child(2) > :nth-child(2) > a').should('have.attr', 'href').and('include', 'mailto:'); }) - cy.get('.dataset-about-info > .mb-16').contains(/Award[(]s[)]: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Funding Program[(]s[)]: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Associated project[(]s[)]: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Institution[(]s[)]: (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Version (.+) Revision (.+): (.+)/).should('exist') - cy.get('.dataset-about-info > .mb-16').contains(/Dataset DOI: (.+)/).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Award[(]s[)]: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Funding Program[(]s[)]: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Associated project[(]s[)]: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Institution[(]s[)]: (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Version (.+) Revision (.+): (.+)/i).should('exist') + cy.get('.dataset-about-info > .mb-16').contains(/Dataset DOI: (.+)/i).should('exist') /** * Contact Author may not be the contributor @@ -216,7 +236,7 @@ datasetIds.forEach(datasetId => { cy.get(':nth-child(8) > :nth-child(2) > a').should('have.attr', 'href', value); }); - cy.get('.dataset-about-info').contains(/Institution[(]s[)]: (.+)/).children().not('.label4').invoke('text').then((institution) => { + cy.get('.dataset-about-info').contains(/Institution[(]s[)]: (.+)/i).children().not('.label4').invoke('text').then((institution) => { cy.get('.mt-8 > a').click() cy.url({ timeout: 30000 }).should('contain', 'projects') @@ -225,6 +245,9 @@ datasetIds.forEach(datasetId => { cy.get('.row > .heading2').should('contain', title.trim()); cy.get(':nth-child(4) > .label4').should('contain', institution.trim()); cy.go('back') + + cy.waitForLoadingMask() + }) }) } @@ -232,6 +255,15 @@ datasetIds.forEach(datasetId => { }); it("Cite Tab", function () { + cy.url().then((url) => { + if (!url.includes(`/datasets/${datasetId}?type=dataset`)) { + cy.go('back') + + cy.waitForLoadingMask() + + } + }) + // Should switch to 'Cite' cy.get('#datasetDetailsTabsContainer > .style1', { timeout: 30000 }).contains('Cite').click(); cy.get('.active.style1.tab2.tab-link.p-16').should('contain', 'Cite'); @@ -268,7 +300,7 @@ datasetIds.forEach(datasetId => { cy.get('.left-column .el-button').contains('Download full dataset').should('be.visible'); cy.contains('Dataset size').parent().then(($size) => { const size = parseFloat($size.text().match(/[0-9]+(.[0-9]+)?/i)[0]) - if ($size.text().includes("GB") && size > 5) { + if (($size.text().includes("GB") && size > 5) || $size.text().includes("TB")) { cy.get('.el-tooltip__trigger > .el-button').should('not.be.enabled') } else { cy.get('.left-column > :nth-child(1) > a > .el-button').should('be.enabled') @@ -297,12 +329,16 @@ datasetIds.forEach(datasetId => { cy.get('.inline > .dataset-link').should('have.attr', 'href', 'https://docs.sparc.science/docs/navigating-a-sparc-dataset'); cy.get('.breadcrumb-link').should('have.class', 'breadcrumb-link'); cy.get('.breadcrumb-link').should('have.attr', 'href').and('contain', 'datasetDetailsTab=files&path=files'); - cy.get('.cell > .file-name-wrap > .el-tooltip__trigger').then(($folder) => { - cy.get('.breadcrumb-link').should('have.length', 1) - cy.wrap($folder).first().click() - cy.get('.breadcrumb-link').should('have.length', 2) - cy.get(':nth-child(1) > .breadcrumb-link').click() - cy.get('.breadcrumb-link').should('have.length', 1) + cy.get('tbody').then(($ele) => { + if ($ele.text().includes('Folder')) { + cy.get('.cell > .file-name-wrap > .el-tooltip__trigger').then(($folder) => { + cy.get('.breadcrumb-link').should('have.length', 1) + cy.wrap($folder).first().click() + cy.get('.breadcrumb-link').should('have.length', 2) + cy.get(':nth-child(1) > .breadcrumb-link').click() + cy.get('.breadcrumb-link').should('have.length', 1) + }) + } }) } else { this.skip(); @@ -319,7 +355,7 @@ datasetIds.forEach(datasetId => { cy.get('.active.style1.tab2.tab-link.p-16').should('contain', 'References'); // Check for content - cy.get('.dataset-references .heading2').contains('Associated Publications for this Dataset'); + cy.get('.dataset-references .heading2').contains(/Primary Publications for this Dataset|Associated Publications for this Dataset/); cy.get('.dataset-references .citation-container').each(($el) => { cy.wrap($el).find('div > a').should('have.attr', 'href').and('include', 'doi.org'); cy.wrap($el).find('.copy-button').click(); @@ -367,6 +403,8 @@ datasetIds.forEach(datasetId => { cy.wrap($cell).find('.circle').as('icons').should('have.length', 2) cy.get('@icons').eq(0).click() + cy.wait(5000) + // Check for changelog popover cy.get('.optional-content-container').should('be.visible'); cy.get('.main-content-container').should('be.visible'); @@ -395,6 +433,8 @@ datasetIds.forEach(datasetId => { }); }) }) + } else { + this.skip() } }); }); diff --git a/tests/cypress/e2e/mapsviewer.cy.js b/tests/cypress/e2e/mapsviewer.cy.js index 937ccf02..1c96c735 100644 --- a/tests/cypress/e2e/mapsviewer.cy.js +++ b/tests/cypress/e2e/mapsviewer.cy.js @@ -40,7 +40,6 @@ describe('Maps Viewer', { testIsolation: false }, function () { if (index === 0) { cy.wait('@flatmap', { timeout: 20000 }) - cy.waitForLoadingMask() loadedModels.add('Rat') @@ -54,7 +53,6 @@ describe('Maps Viewer', { testIsolation: false }, function () { if (!loadedModels.has(model)) { cy.wait('@flatmap', { timeout: 20000 }) - cy.waitForLoadingMask() loadedModels.add(model) @@ -68,24 +66,22 @@ describe('Maps Viewer', { testIsolation: false }, function () { cy.get('[style="height: 100%;"] > [style="height: 100%; width: 100%; position: relative;"] > [style="height: 100%; width: 100%;"] > .maplibregl-touch-drag-pan > .maplibregl-canvas').as('canvas'); // Open a provenance card cy.clickNeuron(coordinate, pixelChange) + + cy.visit('/maps?type=ac') }) }) it(`From 2D ${threeDSyncView}, open 3D map for synchronised view and Search within display`, function () { + cy.wait('@flatmap', { timeout: 20000 }) cy.waitForLoadingMask() // Switch to the human related flatmap cy.get('.el-select.select-box.el-tooltip__trigger.el-tooltip__trigger').click() cy.get('.el-select-dropdown__item').contains(new RegExp(threeDSyncView, 'i')).click({ force: true }) - if (!loadedModels.has(threeDSyncView)) { - - cy.wait('@flatmap', { timeout: 20000 }) - - cy.waitForLoadingMask() - - } + cy.wait('@flatmap', { timeout: 20000 }) + cy.waitForLoadingMask() // Open the 3D view in a split viewer cy.get('.settings-group > :nth-child(1):visible').contains(/Open new map/i).should('exist') @@ -136,7 +132,6 @@ describe('Maps Viewer', { testIsolation: false }, function () { cy.get('.header > .el-button > span').click() cy.wait('@query', { timeout: 20000 }) - cy.waitForLoadingMask() cy.get('.dataset-results-feedback', { timeout: 30000 }).then(($result) => { @@ -163,7 +158,7 @@ describe('Maps Viewer', { testIsolation: false }, function () { // Check for context card cy.get('.context-card').should('be.visible') - cy.get('.context-image:visible').should('have.attr', 'src').and('contain', `https://assets.discover.pennsieve.io/dataset-assets/${datasetId}`) + cy.get('.context-image:visible').should('have.attr', 'src').and('contain', datasetId) cy.get('[style="margin-right: 8px;"] > .title:visible').should('have.class', 'title') } }) diff --git a/tests/cypress/e2e/userstories.cy.js b/tests/cypress/e2e/userstories.cy.js index e35c8ec9..e1a4966a 100644 --- a/tests/cypress/e2e/userstories.cy.js +++ b/tests/cypress/e2e/userstories.cy.js @@ -102,7 +102,6 @@ describe('User stories', function () { cy.visit(`/maps?type=scaffold&dataset_id=${datasetId}&dataset_version=${version}`); cy.wait('@query', { timeout: 20000 }); - cy.waitForLoadingMask() // Search dataset id @@ -117,6 +116,9 @@ describe('User stories', function () { cy.get('@datasetCards').filter(`:contains(${datasetId})`).within(() => { cy.get('.badges-container > .container', { timeout: 30000 }).contains(/Scaffold/i).click(); + + cy.waitForLoadingMask() + }); cy.get('@datasetCards').contains(/View Scaffold/i).click(); @@ -151,7 +153,7 @@ describe('User stories', function () { cy.wait(5000) }) - + categories.forEach((category) => { it(`Filter datasets by ${category}`, function () { @@ -165,7 +167,7 @@ describe('User stories', function () { cy.get('@facetsCategory').click(); cy.wait('@query', { timeout: 20000 }); - + cy.get('.cell > :nth-child(1) > .property-table > :nth-child(1) > :nth-child(2)', { timeout: 30000 }).first().contains(regex).should('exist'); // Check for detail page diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 3bd78b8b..1503dba3 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -37,6 +37,10 @@ Cypress.on('uncaught:exception', (err, runnable) => { return false if (err.message.includes('ResizeObserver loop completed with undelivered notifications')) return false + if (err.message.includes('path.dirname is not a function')) + return false + if (err.message.includes("Cannot destructure property 'type' of 'vnode' as it is null")) + return false // // For legacy dataset // if (err.message.includes('ObjectID does not exist')) // return false @@ -101,4 +105,30 @@ Cypress.Commands.add('waitForLoadingMask', () => { cy.wait(5000) +}) + +Cypress.Commands.add('filterCheckbox', (factArray, action, checkbox) => { + factArray.forEach((facet) => { + // Check the matched facet checkbox + cy.wrap(checkbox).contains(new RegExp(`^${facet}$`, 'i')).then(($label) => { + cy.wrap($label).parent().siblings('.el-checkbox').then(($checkbox) => { + const isChecked = $checkbox.hasClass('is-checked') + const isIndeterminate = $checkbox.children().hasClass('is-indeterminate') + if (action === 'check' && !isChecked) { + cy.wrap($label).click() + } else if (action === 'uncheck' && (isChecked || isIndeterminate)) { + cy.wrap($label).click() // If isIndeterminate after this click checkbox will turn to isChecked + if (isIndeterminate) { + cy.wrap($label).click() // One more click to uncheck + } + } + }) + }) + }) +}) + +Cypress.Commands.add('checkFilterCleared', () => { + cy.get('.el-card__body > .capitalize').should('not.exist') + cy.get('.no-facets').should('contain', 'No filters applied') + cy.url().should('not.contain', 'selectedFacetIds') }) \ No newline at end of file diff --git a/tests/cypress/support/dynamicConfig.js b/tests/cypress/support/dynamicConfig.js new file mode 100644 index 00000000..af9771c1 --- /dev/null +++ b/tests/cypress/support/dynamicConfig.js @@ -0,0 +1,67 @@ +import axios from 'axios' + +function randomAssign(itemString, number) { + let itemArray = [] + const items = [...new Set(itemString.split(',').map(item => item.trim()).filter(item => item))] + while (itemArray.length < number) { + const item = items[Math.floor(Math.random() * items.length)] + if (!itemArray.includes(item)) { + itemArray.push(item) + } + } + return itemArray.join(', ') +} + +async function generateDatasetIds(config) { + let datasetIds + await axios + .get(`${config.env.PORTAL_API}/all_dataset_ids`) + .then((response) => { + datasetIds = randomAssign(response.data, 5) + }) + .catch((error) => { + console.error('Error:', error.message) + }); + return datasetIds +} + +export default async function dynamicConfig(config) { + for (const env in DYNAMICENVS) { + if (!config.env[env]) { + config.env[env] = randomAssign(DYNAMICENVS[env].value, DYNAMICENVS[env].number) + } + } + if (!config.env.DATASET_IDS) { + config.env.DATASET_IDS = await generateDatasetIds(config) + } + return config +} + +const DYNAMICENVS = { + 'PAGE_LIMIT': { 'value': '10, 20, 50, View All', 'number': 1 }, // fixed, no need to change + 'SEARCH_KEYWORDS': { + 'value': 'Vagus, Spine, Heart, Microscopy, Electrophysiology, Pig', + 'number': 2 + }, + 'FILTER_FACET': { + 'value': 'Central Nervous System, Brain, Human, Connectivity, Male, Adult', + 'number': 1 // fixed, no need to change + }, + 'MULTIPLE_FILTER_FACETS': { + 'value': 'Central Nervous System, Brain, Human, Connectivity, Male, Adult', + 'number': 2 + }, + 'TAXON_MODELS': { + 'value': 'Human Female, Human Male, Rat, Mouse, Pig, Cat', // fixed, no need to change + 'number': 2 + }, + 'THREE_SYNC_VIEW': { 'value': 'Human Female, Human Male', 'number': 1 }, // fixed, no need to change + 'SEARCH_IN_MAP': { + 'value': 'Heart, Lung, Colon, Stomach, Liver', + 'number': 1 // fixed, no need to change + }, + 'SCAFFOLD_DATASET_IDS': { + 'value': '150, 155, 292, 102, 223', + 'number': 2 + }, +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bd2325af..fb23c86b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9089,10 +9089,10 @@ custom-event-polyfill@^1.0.7: resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee" integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w== -cypress@^13.6.6: - version "13.6.6" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.6.6.tgz#5133f231ed1c6e57dc8dcbf60aade220bcd6884b" - integrity sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A== +cypress@13.9.0: + version "13.9.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.9.0.tgz#b529cfa8f8c39ba163ed0501a25bb5b09c143652" + integrity sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ== dependencies: "@cypress/request" "^3.0.0" "@cypress/xvfb" "^1.2.4"