diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4cae910c30..7bc9f244ca 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -321,7 +321,18 @@ jobs: run: | scripts/install_gitea.sh + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Pre e2e + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FRONTEND_DEPLOY_IMAGE_TAG: ':${{ needs.get_version.outputs.frontend_sha_short }}' + PROCESSOR_DEPLOY_IMAGE_TAG: ':${{ needs.get_version.outputs.processor_sha_short }}' + NGINX_DEPLOY_IMAGE_TAG: ':${{ needs.get_version.outputs.nginx_sha_short }}' + GITEA_DEPLOY_IMAGE_TAG: ':${{ needs.get_version.outputs.gitea_sha_short }}' shell: bash run: scripts/pre_e2e.sh @@ -344,6 +355,7 @@ jobs: COMMIT_INFO_SHA: '${{ github.event.head_commit.id }}' COMMIT_INFO_TIMESTAMP: '${{ github.event.head_commit.timestamp }}' COMMIT_INFO_BRANCH: '${{ github.ref_name || github.head_ref }}' + CYPRESS_ADMIN_TOKEN: ${{ env.CYPRESS_ADMIN_TOKEN }} run: | docker-compose -f docker-compose.yml -f docker-compose.deploy.yml -f docker-compose.e2e.yml up \ --abort-on-container-exit --exit-code-from e2e diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 2933cc5918..762a2101f5 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -22,6 +22,8 @@ services: - COMMIT_INFO_SHA=${COMMIT_INFO_SHA} - COMMIT_INFO_TIMESTAMP=${COMMIT_INFO_TIMESTAMP} - COMMIT_INFO_BRANCH=${COMMIT_INFO_BRANCH} + - CYPRESS_ADMIN_TOKEN=${CYPRESS_ADMIN_TOKEN} + depends_on: - nginx working_dir: /e2e diff --git a/e2e/cypress/integration/IBOM.spec.js b/e2e/cypress/integration/IBOM.spec.js index 2fa4f0dfae..4bb214917d 100644 --- a/e2e/cypress/integration/IBOM.spec.js +++ b/e2e/cypress/integration/IBOM.spec.js @@ -10,28 +10,17 @@ describe('IBOM page', () => { }) it('should redirect to project page (multi project)', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const user = getFakeUser() const multiProjectsNames = ['alpha-spectrometer', 'electron-detector'] const multiProjectsRepoName = 'DIY_particle_detector' const syncedRepoUrlMultiProjects = 'https://github.com/kitspace-test-repos/DIY_particle_detector' - /* Migrate the multiproject repo */ - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) + + cy.forceVisit(`/${user.username}/${multiProjectsRepoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should( - 'contain', - `${username}/${multiProjectsRepoName}`, - ) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 120_000 }).as('projectCards') @@ -39,7 +28,7 @@ describe('IBOM page', () => { // Go to IBOM page for a subproject const subProjectName = multiProjectsNames[0] - cy.visit(`${username}/${multiProjectsRepoName}/${subProjectName}/IBOM`) + cy.visit(`${user.username}/${multiProjectsRepoName}/${subProjectName}/IBOM`) // Click on the title cy.get('#title').click() @@ -47,36 +36,26 @@ describe('IBOM page', () => { // It should redirect to the subproject page cy.url({ timeout: 20_000 }).should( 'eq', - `${ - Cypress.config().baseUrl - }/${username}/${multiProjectsRepoName}/${subProjectName}`, + `${Cypress.config().baseUrl}/${ + user.username + }/${multiProjectsRepoName}/${subProjectName}`, ) }) it('should redirect to the project page (normal project)', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - /* Migrate the normal repo */ - cy.forceVisit('/projects/new') + const user = getFakeUser() const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' const normalRepoName = 'CH330_Hardware' + cy.importRepo(syncedRepoUrl, normalRepoName, user) - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() - - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${normalRepoName}`) + cy.forceVisit(`/${user.username}/${normalRepoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') // Go to IBOM page for a single (not multi) project cy.get('[data-cy=ibom]').should('be.visible') - cy.visit(`${username}/${normalRepoName}/_/IBOM`) + cy.visit(`${user.username}/${normalRepoName}/_/IBOM`) // Click on the title cy.get('#title').click() @@ -84,24 +63,20 @@ describe('IBOM page', () => { // It should redirect to the project page cy.url({ timeout: 20_000 }).should( 'eq', - `${Cypress.config().baseUrl}/${username}/${normalRepoName}`, + `${Cypress.config().baseUrl}/${user.username}/${normalRepoName}`, ) }) it('should not render `Assembly Guide` button for projects with `ibom-enabled: false', () => { - const { username, email, password } = getFakeUser() - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') + const user = getFakeUser() /* Migrate a repo with `omit-ibom` set to `true` */ - cy.forceVisit('/projects/new') + const repoName = 'solarbird_shenzen_rdy' const IBOMDisabledRepoURL = 'https://github.com/kitspace-test-repos/solarbird_shenzen_rdy' - const repoName = 'solarbird_shenzen_rdy' - cy.get('[data-cy=sync-field]').type(IBOMDisabledRepoURL) - cy.get('button').contains('Sync').click() - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.importRepo(IBOMDisabledRepoURL, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) + cy.url({ timeout: 60_000 }).should('contain', `${user.username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 100_000 }).should('be.visible') // The ibom button shouldn't get rendered for this project. diff --git a/e2e/cypress/integration/IBOM.visual.spec.js b/e2e/cypress/integration/IBOM.visual.spec.js index 2cd332f887..3fd5319e94 100644 --- a/e2e/cypress/integration/IBOM.visual.spec.js +++ b/e2e/cypress/integration/IBOM.visual.spec.js @@ -2,27 +2,17 @@ import { getFakeUser } from '../support/getFakeUser' describe.skip('Regression test for IBOM ', () => { before(() => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'CH330_Hardware' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() - - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.importRepo(syncedRepoUrl, repoName, user) + cy.forceVisit(`/${user.username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') - cy.visit(`${username}/${repoName}/IBOM`) + cy.visit(`${user.username}/${repoName}/IBOM`) // Wait until the ibom is visible cy.get('.topmostdiv').should('be.visible') diff --git a/e2e/cypress/integration/legacyRedirect.spec.js b/e2e/cypress/integration/legacyRedirect.spec.js index 0ab86d0203..13d643d133 100644 --- a/e2e/cypress/integration/legacyRedirect.spec.js +++ b/e2e/cypress/integration/legacyRedirect.spec.js @@ -10,27 +10,16 @@ describe('Legacy redirects', () => { }) it('should redirect `/boards/*/user/project/*` to `/user/project/*`', () => { - const { username, email, password } = getFakeUser() - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const user = getFakeUser() const multiProjectsRepoName = 'DIY_particle_detector' const multiProjectsNames = ['alpha-spectrometer', 'electron-detector'] const syncedRepoUrlMultiProjects = 'https://github.com/kitspace-test-repos/DIY_particle_detector' - /* Migrate the multiproject repo */ - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should( - 'contain', - `${username}/${multiProjectsRepoName}`, - ) + cy.forceVisit(`/${user.username}/${multiProjectsRepoName}`) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 120_000 }).as('projectCards') @@ -38,38 +27,27 @@ describe('Legacy redirects', () => { /* Legacy redirect */ cy.visit( - `/boards/github.com/${username}/${multiProjectsRepoName}/${multiProjectsNames[0]}`, + `/boards/github.com/${user.username}/${multiProjectsRepoName}/${multiProjectsNames[0]}`, ) cy.url().should( 'eq', - `${Cypress.config().baseUrl}/${username}/${multiProjectsRepoName}/${ + `${Cypress.config().baseUrl}/${user.username}/${multiProjectsRepoName}/${ multiProjectsNames[0] }`, ) }) it('should redirect `/interactive_bom/?*/user/project*` to `/user/project/IBOM`', () => { - const { username, email, password } = getFakeUser() - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - + const user = getFakeUser() const multiProjectsRepoName = 'DIY_particle_detector' const multiProjectsNames = ['alpha-spectrometer', 'electron-detector'] const syncedRepoUrlMultiProjects = 'https://github.com/kitspace-test-repos/DIY_particle_detector' /* Migrate the multiproject repo */ - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should( - 'contain', - `${username}/${multiProjectsRepoName}`, - ) + cy.forceVisit(`/${user.username}/${multiProjectsRepoName}`) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 120_000 }).as('projectCards') @@ -77,11 +55,11 @@ describe('Legacy redirects', () => { /* Legacy redirect */ cy.visit( - `/interactive_bom/?github.com/${username}/${multiProjectsRepoName}/${multiProjectsNames[0]}`, + `/interactive_bom/?github.com/${user.username}/${multiProjectsRepoName}/${multiProjectsNames[0]}`, ) cy.url().should( 'eq', - `${Cypress.config().baseUrl}/${username}/${multiProjectsRepoName}/${ + `${Cypress.config().baseUrl}/${user.username}/${multiProjectsRepoName}/${ multiProjectsNames[0] }/IBOM`, ) diff --git a/e2e/cypress/integration/multiProject.spec.js b/e2e/cypress/integration/multiProject.spec.js index 33ffd16184..d0bb052e7b 100644 --- a/e2e/cypress/integration/multiProject.spec.js +++ b/e2e/cypress/integration/multiProject.spec.js @@ -17,22 +17,13 @@ describe('Render project cards', () => { }) it('should render a card for each multiproject', () => { - const { username, email, password } = getFakeUser() - + const user = getFakeUser() const repoName = syncedRepoUrlMultiProjects.split('/').slice(-1).toString() - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the multiproject repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, repoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.forceVisit(`/${user.username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 60_000 }).should( 'have.length', @@ -40,46 +31,29 @@ describe('Render project cards', () => { ) // should render a card for each multiproject - cy.visit(`/${username}`) + cy.visit(`/${user.username}`) multiProjectsNames.forEach(name => { cy.get('[data-cy=project-card]').contains(name) }) }) - it('should display card thumbnail', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + it.only('should display card thumbnail', () => { + const user = getFakeUser() // Migrate the multiproject repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should( - 'contain', - `${username}/${multiProjectsRepoName}`, - ) + cy.forceVisit(`/${user.username}/${multiProjectsRepoName}`) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 60_000 }).should( 'have.length', multiProjectsNames.length, ) - /* Migrate the normal repo */ - cy.forceVisit('/projects/new') - - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() - - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${normalRepoName}`) + // Migrate the normal repo + cy.importRepo(syncedRepoUrl, normalRepoName, user) - cy.visit(`/${username}`) + cy.visit(`/${user.username}`) // There should be 3 cards = 2 form multiprojects + 1 normal project cy.get('[data-cy=project-card]', { timeout: 60_000 }).should( 'have.length', @@ -88,23 +62,13 @@ describe('Render project cards', () => { }) it('should redirect to the multi project page', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const user = getFakeUser() /* Migrate the multiproject repo */ - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) + + cy.forceVisit(`/${user.username}/${multiProjectsRepoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should( - 'contain', - `${username}/${multiProjectsRepoName}`, - ) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 60_000 }).should( 'have.length', @@ -115,45 +79,34 @@ describe('Render project cards', () => { cy.visit('/') // Search for the project - cy.get('nav [data-cy=search-field] > input').type(username) + cy.get('nav [data-cy=search-field] > input').type(user.username) cy.get('nav [data-cy=search-form]').submit() // Click on a subproject project card cy.get('[data-cy=project-card]').within(() => { - cy.contains(username) + cy.contains(user.username) cy.contains(multiProjectName).click({ force: true }) }) - // Should redirect to the `[username]/[projectName]/[multiProject]` + // Should redirect to the `[user.username]/[projectName]/[multiProject]` cy.url({ timeout: 20_000 }).should( 'contain', - `${username}/${multiProjectsRepoName}/${multiProjectName}`, + `${user.username}/${multiProjectsRepoName}/${multiProjectName}`, ) }) it('should handle subproject names with URL invalid characters', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - + const user = getFakeUser() const projectWithInvalidCharsURL = 'https://github.com/kitspace-test-repos/open-visual-stimulator' const projectWithInvalidCharsName = 'open-visual-stimulator' const numberOfSubProjects = 4 /* Migrate the multiproject repo */ - cy.get('[data-cy=sync-field]').type(projectWithInvalidCharsURL) - cy.get('button').contains('Sync').click() + cy.importRepo(projectWithInvalidCharsURL, projectWithInvalidCharsName, user) + + cy.forceVisit(`/${user.username}/${projectWithInvalidCharsName}`) - // Wait for redirection for project page - cy.url({ timeout: 120_000 }).should( - 'contain', - `${username}/${projectWithInvalidCharsName}`, - ) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 60_000 }).should( 'have.length', @@ -175,21 +128,14 @@ describe('Multi project page', () => { }) it('should render the page components', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const user = getFakeUser() // Migrate the multiproject repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) cy.url({ timeout: 60_000 }).should( 'contain', - `${username}/${multiProjectsRepoName}`, + `${user.username}/${multiProjectsRepoName}`, ) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 60_000 }).as('projectCards') @@ -201,7 +147,7 @@ describe('Multi project page', () => { cy.get('@projectCards').contains(subProjectName).click() cy.url({ timeout: 20_000 }).should( 'contain', - `${username}/${multiProjectsRepoName}/${subProjectName}`, + `${user.username}/${multiProjectsRepoName}/${subProjectName}`, ) // Different page elements should be visible. @@ -224,21 +170,14 @@ describe('Multi project page', () => { }) it('should render the details from multi project in kitspace.yaml', () => { - const { username, email, password } = getFakeUser() - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const user = getFakeUser() // Migrate the multiproject repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrlMultiProjects) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrlMultiProjects, multiProjectsRepoName, user) cy.url({ timeout: 10_000 }).should( 'contain', - `${username}/${multiProjectsRepoName}`, + `${user.username}/${multiProjectsRepoName}`, ) // Wait for the repo to finish processing, by checking the visibility sub projects cards. cy.get('[data-cy=project-card]', { timeout: 120_000 }).should( @@ -249,17 +188,17 @@ describe('Multi project page', () => { const multiProjectName = multiProjectsNames[0] cy.visit('/') // Search for the project - cy.get('nav [data-cy=search-field] > input').type(username) + cy.get('nav [data-cy=search-field] > input').type(user.username) cy.get('nav [data-cy=search-form]').submit() // Click on a multiproject project card cy.get('[data-cy=project-card]').within(() => { - cy.contains(username) + cy.contains(user.username) cy.contains(multiProjectName).click({ force: true }) }) cy.url({ timeout: 20_000 }).should( 'contain', - `${username}/${multiProjectsRepoName}/${multiProjectName}`, + `${user.username}/${multiProjectsRepoName}/${multiProjectName}`, ) // The info bar should have the correct title. cy.get('[data-cy=project-title]').should('have.text', multiProjectName) diff --git a/e2e/cypress/integration/readme.spec.js b/e2e/cypress/integration/readme.spec.js index 34de589aa5..dab9454ae9 100644 --- a/e2e/cypress/integration/readme.spec.js +++ b/e2e/cypress/integration/readme.spec.js @@ -10,24 +10,15 @@ describe('Relative README images URLs normalization', () => { }) it('handles readme images starting with `/`', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'CH330_Hardware' - const syncedRepoUrl = - 'https://github.com/kitspace-test-repos/CH330_Hardware' - - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') + const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.visit(`${user.username}/${repoName}`) cy.get('[data-cy=readme-img]', { timeout: 60_000 }) .should('have.attr', 'src') @@ -43,27 +34,19 @@ describe('Relative README images URLs normalization', () => { }) it('handles readmes in folders', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'readmes-in-folders' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/readmes-in-folders' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.visit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.visit(`${user.username}/${repoName}`) cy.get('[data-cy=project-card]', { timeout: 20_000 }).should('have.length', 2) - cy.visit(`/${username}/${repoName}/project_2`) + cy.visit(`/${user.username}/${repoName}/project_2`) cy.get('[data-cy=readme]').within(() => { cy.get('img').each($img => { // checks for naturalWidth/naturalHeight are not working @@ -79,27 +62,19 @@ describe('Relative README images URLs normalization', () => { }) it('redirects relative urls to original git service', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'readmes-in-folders' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/readmes-in-folders' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.visit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.visit(`${user.username}/${repoName}`) cy.get('[data-cy=project-card]', { timeout: 20_000 }).should('have.length', 2) - cy.visit(`/${username}/${repoName}/project_1`) + cy.visit(`/${user.username}/${repoName}/project_1`) cy.get('[data-cy=readme]').within(() => { cy.get('a').each($a => { @@ -111,24 +86,17 @@ describe('Relative README images URLs normalization', () => { describe('Readme style', () => { it('should auto link readme and summary links', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'readme-and-summary-auto-link' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/readme-and-summary-auto-link' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.visit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') @@ -150,23 +118,16 @@ describe('Readme style', () => { }) it('renders :emoji: in readme and project description', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'ogx360' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/ogx360' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.visit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') @@ -176,23 +137,16 @@ describe('Readme style', () => { }) it('preserves URLs for GitHub Actions badges', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'ogx360' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/ogx360' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.visit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') diff --git a/e2e/cypress/integration/search.spec.js b/e2e/cypress/integration/search.spec.js index f180cc78f1..afc1693411 100644 --- a/e2e/cypress/integration/search.spec.js +++ b/e2e/cypress/integration/search.spec.js @@ -17,23 +17,16 @@ describe('Navbar search', () => { }) it('should display project card on submitting search form', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'CH330_Hardware' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish migration, by checking the visibility of processing-loader. cy.get('[data-cy=processing-loader]', { timeout: 60_000 }) // Wait for the repo to finish processing, by checking the visibility of info-bar. @@ -52,23 +45,16 @@ describe('Navbar search', () => { describe('Homepage search on mobile', () => { it('should display project card on submitting search form', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'CH330_Hardware' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field] > input').type(syncedRepoUrl, { force: true }) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish migration, by checking the visibility of processing-loader. cy.get('[data-cy=processing-loader]', { timeout: 60_000 }) // Wait for the repo to finish processing, by checking the visibility of info-bar. @@ -97,23 +83,16 @@ describe('/search route', () => { }) it('should use `q` from query parameters', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'HACK' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/HACK' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish migration, by checking the visibility of processing-loader. cy.get('[data-cy=processing-loader]', { timeout: 60_000 }) // Wait for the repo to finish processing, by checking the visibility of info-bar. diff --git a/e2e/cypress/integration/syncRepo.spec.js b/e2e/cypress/integration/syncRepo.spec.js index 0d2a184574..32f5202b14 100644 --- a/e2e/cypress/integration/syncRepo.spec.js +++ b/e2e/cypress/integration/syncRepo.spec.js @@ -9,13 +9,13 @@ describe('Syncing a project behavior validation', () => { cy.visit('/') }) - it('should sync a repo on gitea', () => { + it.skip('should sync a repo via UI', () => { const syncedRepoUrl = 'https://github.com/AbdulrhmnGhanem/light-test-repo' const repoName = 'light-test-repo' - const { username, email, password } = getFakeUser() + const user = getFakeUser() - cy.createUser(username, email, password) + cy.createUser(user.username, email, password) cy.visit('/') cy.get('[data-cy=user-menu]') @@ -25,32 +25,24 @@ describe('Syncing a project behavior validation', () => { cy.get('[data-cy=sync-result-message]').should('have.class', 'green') // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.url({ timeout: 60_000 }).should('contain', `${user.username}/${repoName}`) cy.get('[data-cy=project-title]') - cy.visit(`/${username}`) + cy.visit(`/${user.username}`) // assert the repo is on the user's project page cy.get('[data-cy=project-card]').contains(repoName) }) it('should display original url for synced repos', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'CH330_Hardware' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) + cy.forceVisit(`/${user.username}/${repoName}`) // The info bar should include the original url cy.get('[data-cy=original-url] > a', { timeout: 60_000 }) @@ -61,24 +53,17 @@ describe('Syncing a project behavior validation', () => { ) }) - it.skip('should fall back to repo description on missing `summary` key in `kitspace.yaml`', () => { - const { username, email, password } = getFakeUser() + it('should fall back to repo description on missing `summary` key in `kitspace.yaml`', () => { + const user = getFakeUser() const repoName = 'ArduTouch' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/ArduTouch' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') @@ -86,32 +71,25 @@ describe('Syncing a project behavior validation', () => { cy.get('[data-cy=project-description]').should('contain', 'ARDUTOUCH ') }) - it.skip('should fall back to repo description in `ProjectCard` on missing `summary` in `kitspace.yaml', () => { - const { username, email, password } = getFakeUser() + it('should fall back to repo description in `ProjectCard` on missing `summary` in `kitspace.yaml', () => { + const user = getFakeUser() const repoName = 'CH330_Hardware_without_summary' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/CH330_Hardware_without_summary' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=info-bar]', { timeout: 60_000 }).should('be.visible') // Go to user projects page to limit the results to the project synced above - cy.visit(`/${username}`) + cy.visit(`/${user.username}`) - cy.get('[data-cy=project-card]').should('contain', username).and( + cy.get('[data-cy=project-card]').should('contain', user.username).and( 'contain.text', // This is github repo description. 'To test that meilisearch falls back to repo description', @@ -119,27 +97,20 @@ describe('Syncing a project behavior validation', () => { }) it.skip('should escape and render project description correctly', () => { - const { username, email, password } = getFakeUser() + const user = getFakeUser() const repoName = 'LED-Zappelin' const syncedRepoUrl = 'https://github.com/kitspace-test-repos/LED-Zappelin' - cy.createUser(username, email, password) - cy.visit('/') - cy.get('[data-cy=user-menu]') - - cy.forceVisit('/projects/new') - // Migrate the repo - cy.get('[data-cy=sync-field]').type(syncedRepoUrl) - cy.get('button').contains('Sync').click() + cy.importRepo(syncedRepoUrl, repoName, user) + + cy.forceVisit(`/${user.username}/${repoName}`) - // Wait for redirection for project page - cy.url({ timeout: 60_000 }).should('contain', `${username}/${repoName}`) // Wait for the repo to finish processing, by checking the visibility of info-bar. cy.get('[data-cy=project-card]', { timeout: 60_000 }) // Go to the PCB stimulator sub-project - cy.visit(`${username}/${repoName}/PCB-Stimulator`) + cy.visit(`${user.username}/${repoName}/PCB-Stimulator`) cy.get('[data-cy=project-description]').should( 'contain', diff --git a/e2e/cypress/support/commands.js b/e2e/cypress/support/commands.js index 9876e079d9..1be4c4043e 100644 --- a/e2e/cypress/support/commands.js +++ b/e2e/cypress/support/commands.js @@ -1,3 +1,5 @@ +import 'cypress-wait-until' + const signUpEndpoint = 'http://gitea.kitspace.test:3000/user/kitspace/sign_up' const signInEndpoint = 'http://gitea.kitspace.test:3000/user/kitspace/sign_in' @@ -59,3 +61,91 @@ Cypress.Commands.add('forceVisit', (path, timeout) => { cy.visit('/', { timeout }) cy.window().then(win => win.open(path, '_self')) }) + +Cypress.Commands.add('importRepo', (remoteUrl, repoName, user) => { + cy.createGiteaUser(user).then(giteaUser => { + cy.mirrorRepo(remoteUrl, repoName, giteaUser) + }) + cy.waitForStatusCode( + `http://gitea.kitspace.test:3000/${user.username}/${repoName}`, + ) +}) + +Cypress.Commands.add('mirrorRepo', (remoteRepo, repoName, user) => { + const endpoint = 'http://gitea.kitspace.test:3000/api/v1/repos/migrate' + const headers = { + 'Content-Type': 'application/json', + Authorization: `token ${Cypress.env('ADMIN_TOKEN')}`, + } + + const giteaOptions = { + clone_addr: remoteRepo, + uid: user.id, + repo_name: repoName, + mirror: true, + wiki: false, + private: false, + pull_requests: false, + releases: true, + issues: false, + service: 'github', + } + + cy.request({ + url: endpoint, + method: 'POST', + headers, + body: JSON.stringify(giteaOptions), + failOnStatusCode: false, + }).then(response => { + if (!response.status === 201) { + throw new Error('Failed to mirror repo') + } + }) +}) + +Cypress.Commands.add('createGiteaUser', user => { + const headers = { + 'Content-Type': 'application/json', + Authorization: `token ${Cypress.env('ADMIN_TOKEN')}`, + } + + const url = 'http://gitea.kitspace.test:3000/api/v1/admin/users' + + cy.request({ + url, + method: 'POST', + headers, + body: JSON.stringify(user), + failOnStatusCode: false, + }).then(response => { + if (!response.status === 201) { + throw new Error('Failed to create user') + } + + return response.body + }) +}) + +Cypress.Commands.add( + 'waitForStatusCode', + (url, timeout = 60_000, interval = 1000) => { + cy.log(`Waiting for HTTP request to ${url} to return status 200...`) + + cy.waitUntil( + () => { + return cy + .request({ url, method: 'GET', failOnStatusCode: false }) + .its('status') + .then(status => { + if (status === 200) { + return true + } + cy.log(`Got status ${status}, retrying...`) + return false + }) + }, + { timeout, interval }, + ) + }, +) diff --git a/e2e/cypress/support/index.d.ts b/e2e/cypress/support/index.d.ts index 8bc6b1b0d9..2529fe3ca5 100644 --- a/e2e/cypress/support/index.d.ts +++ b/e2e/cypress/support/index.d.ts @@ -27,7 +27,7 @@ declare namespace Cypress { * @example * cy.signIn(username, password) */ - signIn(username: string, password: string): Chainable + signIn(username: string, password: string): Chainable } interface Chainable { @@ -59,7 +59,7 @@ declare namespace Cypress { * @param newProject if the project is new wait for `@create` and `@upload`. * cy.get('.dropzone').dropFile(file, 'example.txt') */ - dropFiles(files, fileNames: string[], username: string, newProject=true): Chainable + dropFiles(files, fileNames: string[], username: string, newProject: boolean): Chainable } interface Chainable { @@ -70,4 +70,14 @@ declare namespace Cypress { */ forceVisit(path: string, timeout: number): Chainable } + + interface Chainable { + /** + * Import a repo from a url + * @param remoteUrl + * @param repoName + * @param user + */ + importRepo(remoteUrl: string, repoName: string, user: any): Chainable + } } diff --git a/e2e/package.json b/e2e/package.json index 3f54428a5d..7ffb946bcf 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -13,6 +13,7 @@ "cypress-fail-fast": "^3.3.0", "cypress-image-snapshot": "^4.0.1", "cypress-terminal-report": "^3.4.1", + "cypress-wait-until": "^1.7.2", "eslint": ">=7.0.0", "eslint-config-prettier": "8.3.0", "eslint-import-resolver-alias": "^1.1.2", diff --git a/e2e/yarn.lock b/e2e/yarn.lock index 31925c500e..ff697d0b51 100644 --- a/e2e/yarn.lock +++ b/e2e/yarn.lock @@ -505,6 +505,11 @@ cypress-terminal-report@^3.4.1: semver "^7.3.5" tv4 "^1.3.0" +cypress-wait-until@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz#7f534dd5a11c89b65359e7a0210f20d3dfc22107" + integrity sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q== + cypress@^9.4.1: version "9.5.3" resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e" diff --git a/scripts/pre_e2e.sh b/scripts/pre_e2e.sh index 3bb4066a33..692f76c848 100755 --- a/scripts/pre_e2e.sh +++ b/scripts/pre_e2e.sh @@ -6,3 +6,18 @@ set -Eeuo pipefail auto_gen_fixtures_path=e2e/cypress/fixtures/auto-gen mkdir $auto_gen_fixtures_path truncate -s "${MAX_FILE_SIZE}" "${auto_gen_fixtures_path}/big.txt" + +# generate admin token used for importing projects during e2e tests + +if [[ -z $GITHUB_TOKEN ]]; then + # The tests are running locally. + docker-compose up -d +else + docker-compose -f docker-compose.yml -f docker-compose.deploy.yml up -d + until docker logs kitspace_gitea_1 | grep 'ORM engine initialization successful' ; do sleep 3s; done +fi + +token="$(deno run --allow-env --allow-net --allow-run ./scripts/importBoardsTxt.ts --tokenOnly)" +export CYPRESS_ADMIN_TOKEN=$token +echo "CYPRESS_ADMIN_TOKEN=$token" >> $GITHUB_ENV +docker-compose stop