[CE/SP] Add e2e tests for project list (#18475)

* [CE/SP] Add e2e tests for project list

GitOrigin-RevId: 0368d74b47c79f74b3520177d8bb697013d851d4
This commit is contained in:
Miguel Serrano 2024-06-13 17:56:47 +02:00 committed by Copybot
parent df2898a911
commit 0cfc64786d
6 changed files with 232 additions and 6 deletions

View file

@ -14,8 +14,10 @@ describe('admin panel', function () {
const deletedProjectName = `deleted-project-${uuid()}` const deletedProjectName = `deleted-project-${uuid()}`
let projectToDeleteId = '' let projectToDeleteId = ''
const findProjectRow = (projectName: string) => const findProjectRow = (projectName: string) => {
cy.findByText(projectName).parent().parent() cy.log('find project row')
return cy.findByText(projectName).parent().parent()
}
startWith({ startWith({
pro: true, pro: true,

View file

@ -1,4 +1,5 @@
const { defineConfig } = require('cypress') const { defineConfig } = require('cypress')
const { readPdf, readFileInZip } = require('./helpers/read-file')
const specPattern = process.env.SPEC_PATTERN || './**/*.spec.{js,ts,tsx}' const specPattern = process.env.SPEC_PATTERN || './**/*.spec.{js,ts,tsx}'
@ -14,7 +15,10 @@ module.exports = defineConfig({
e2e: { e2e: {
baseUrl: 'http://localhost', baseUrl: 'http://localhost',
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
// implement node event listeners here on('task', {
readPdf,
readFileInZip,
})
}, },
specPattern, specPattern,
}, },

View file

@ -0,0 +1,52 @@
import fs from 'fs'
import path from 'path'
import pdf from 'pdf-parse'
import AdmZip from 'adm-zip'
import { promisify } from 'util'
const sleep = promisify(setTimeout)
const MAX_ATTEMPTS = 15
const POLL_INTERVAL = 500
type ReadFileInZipArgs = {
pathToZip: string
fileToRead: string
}
export async function readFileInZip({
pathToZip,
fileToRead,
}: ReadFileInZipArgs) {
let attempt = 0
while (attempt < MAX_ATTEMPTS) {
if (fs.existsSync(pathToZip)) {
const zip = new AdmZip(path.resolve(pathToZip))
const entry = zip
.getEntries()
.find(entry => entry.entryName == fileToRead)
if (entry) {
return entry.getData().toString('utf8')
} else {
throw new Error(`${fileToRead} not found in ${pathToZip}`)
}
}
await sleep(POLL_INTERVAL)
attempt++
}
throw new Error(`${pathToZip} not found`)
}
export async function readPdf(file: string) {
let attempt = 0
while (attempt < MAX_ATTEMPTS) {
if (fs.existsSync(file)) {
const dataBuffer = fs.readFileSync(path.resolve(file))
const { text } = await pdf(dataBuffer)
return text
}
await sleep(POLL_INTERVAL)
attempt++
}
throw new Error(`${file} not found`)
}

View file

@ -8,13 +8,17 @@
"dependencies": { "dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0", "@isomorphic-git/lightning-fs": "^4.6.0",
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"@types/adm-zip": "^0.5.5",
"@types/pdf-parse": "^1.1.4",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"celebrate": "^15.0.3", "celebrate": "^15.0.3",
"cypress": "13.6.6", "cypress": "13.6.6",
"express": "^4.19.2", "express": "^4.19.2",
"isomorphic-git": "^1.25.10", "isomorphic-git": "^1.25.10",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"pdf-parse": "^1.1.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uuid": "^9.0.1" "uuid": "^9.0.1"
} }
@ -321,6 +325,14 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@types/adm-zip": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz",
"integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/aria-query": { "node_modules/@types/aria-query": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.3.tgz",
@ -330,11 +342,15 @@
"version": "18.18.8", "version": "18.18.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz",
"integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==", "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==",
"optional": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/pdf-parse": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.4.tgz",
"integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg=="
},
"node_modules/@types/sinonjs__fake-timers": { "node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
@ -371,6 +387,14 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/adm-zip": {
"version": "0.5.12",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz",
"integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==",
"engines": {
"node": ">=6.0"
}
},
"node_modules/aggregate-error": { "node_modules/aggregate-error": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@ -2427,6 +2451,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
"integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw=="
},
"node_modules/npm-run-path": { "node_modules/npm-run-path": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -2572,6 +2601,26 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
}, },
"node_modules/pdf-parse": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
"integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==",
"dependencies": {
"debug": "^3.1.0",
"node-ensure": "^0.0.0"
},
"engines": {
"node": ">=6.8.1"
}
},
"node_modules/pdf-parse/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/pend": { "node_modules/pend": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@ -3238,8 +3287,7 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"optional": true
}, },
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.0", "version": "2.0.0",

View file

@ -11,13 +11,17 @@
"dependencies": { "dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0", "@isomorphic-git/lightning-fs": "^4.6.0",
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"@types/adm-zip": "^0.5.5",
"@types/pdf-parse": "^1.1.4",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"celebrate": "^15.0.3", "celebrate": "^15.0.3",
"cypress": "13.6.6", "cypress": "13.6.6",
"express": "^4.19.2", "express": "^4.19.2",
"isomorphic-git": "^1.25.10", "isomorphic-git": "^1.25.10",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"pdf-parse": "^1.1.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uuid": "^9.0.1" "uuid": "^9.0.1"
} }

View file

@ -0,0 +1,116 @@
import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project'
import { startWith } from './helpers/config'
import { v4 as uuid } from 'uuid'
const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
const REGULAR_USER = 'user@example.com'
describe('Project List', () => {
startWith({ pro: true })
const findProjectRow = (projectName: string) => {
cy.log('find project row')
return cy.findByText(projectName).parent().parent()
}
describe('user with no projects', () => {
ensureUserExists({ email: WITHOUT_PROJECTS_USER })
it("'Import from Github' is not displayed in the welcome page", () => {
login(WITHOUT_PROJECTS_USER)
cy.visit('/project')
cy.findByText('Create a new project').click()
cy.findByText(/Import from Github/i).should('not.exist')
})
})
describe('user with projects', () => {
const projectName = `test-project-${uuid()}`
let projectId: string | undefined
ensureUserExists({ email: REGULAR_USER })
before(() => {
login(REGULAR_USER)
cy.visit('/project')
createProject(projectName, { type: 'Example Project' }).then(
id => (projectId = id)
)
})
it('Can download project sources', () => {
login(REGULAR_USER)
cy.visit('/project')
findProjectRow(projectName).within(() =>
cy.get(`[aria-label="Download .zip file"]`).click()
)
cy.task('readFileInZip', {
pathToZip: `cypress/downloads/${projectName}.zip`,
fileToRead: 'main.tex',
}).should('contain', 'Your introduction goes here')
})
it('Can download project PDF', () => {
login(REGULAR_USER)
cy.visit('/project')
findProjectRow(projectName).within(() =>
cy.get(`[aria-label="Download PDF"]`).click()
)
const pdfName = projectName.replaceAll('-', '_')
cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
'contain',
'Your introduction goes here'
)
})
it('can assign and remove tags to projects', () => {
const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
login(REGULAR_USER)
cy.visit('/project')
cy.log('select project')
cy.get(`[id="select-project-${projectId}"`).click()
cy.log('add tag to project')
cy.get('button[aria-label="Tags"]').click()
cy.findByText('Create new tag').click()
cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`)
cy.get(`button[aria-label="Select tag ${tagName}"]`) // tag label in project row
cy.log('remove tag')
cy.get(`button[aria-label="Remove tag ${tagName}"]`)
.first()
.click({ force: true })
cy.get(`button[aria-label="Select tag ${tagName}"]`).should('not.exist')
})
it('can filter by tag', () => {
cy.log('create a separate project to filter')
const nonTaggedProjectName = `project-${uuid()}`
login(REGULAR_USER)
cy.visit('/project')
createProject(nonTaggedProjectName)
cy.visit('/project')
cy.log('select project')
cy.get(`[id="select-project-${projectId}"`).click()
cy.log('add tag to project')
const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
cy.get('button[aria-label="Tags"]').click()
cy.findByText('Create new tag').click()
cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`)
cy.log(
'check the non-tagged project is filtered out after clicking the tag'
)
cy.findByText(nonTaggedProjectName).should('exist')
cy.get('button').contains(tagName).click({ force: true })
cy.findByText(nonTaggedProjectName).should('not.exist')
})
})
})