Skip to content

Commit

Permalink
Merge pull request #5207 from hallieswan/SWC-6556
Browse files Browse the repository at this point in the history
SWC-6556: add end-to-end test for file CRUD
  • Loading branch information
hallieswan authored Oct 20, 2023
2 parents d89d371 + e83ca32 commit e2c13ee
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ src/main/webapp/fonts/*
# If using jenv for JDK management
/.java-version


# Ignore e2e mock data files
/e2e/data/*.csv
4 changes: 4 additions & 0 deletions e2e/data/test_file.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
first,second,third
1,one,true
2,two,false
3,three,true
4 changes: 4 additions & 0 deletions e2e/data/test_file2.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
first,second,third,four
1,one,true,a
2,two,false,b
3,three,true,c
291 changes: 276 additions & 15 deletions e2e/files.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page, expect, test } from '@playwright/test'
import path from 'path'
import { testAuth } from './fixtures/authenticatedUserPages'
import {
createFile,
Expand All @@ -7,8 +8,14 @@ import {
deleteProject,
entityUrlPathname,
generateEntityName,
getEntityFileHandleId,
getEntityIdFromPathname,
} from './helpers/entities'
import { getAccessTokenFromCookie, getAdminPAT } from './helpers/testUser'
import {
dismissAlert,
getAccessTokenFromCookie,
getAdminPAT,
} from './helpers/testUser'
import { Project } from './helpers/types'
import { userConfigs } from './helpers/userConfig'
import { waitForInitialPageLoad } from './helpers/utils'
Expand All @@ -18,11 +25,13 @@ const expectFilePageLoaded = async (
fileEntityId: string,
page: Page,
) => {
await page.waitForURL(entityUrlPathname(fileEntityId))
await expect(page.getByText(`Discussion about ${fileName}`)).toBeVisible()
await testAuth.step('file page is loaded', async () => {
await page.waitForURL(entityUrlPathname(fileEntityId))
await expect(page.getByText(`Discussion about ${fileName}`)).toBeVisible()

await expect(page.getByText('Loading provenance...')).not.toBeVisible()
await expect(page.getByText(fileEntityId, { exact: true })).toBeVisible()
await expect(page.getByText('Loading provenance...')).not.toBeVisible()
await expect(page.getByText(fileEntityId, { exact: true })).toBeVisible()
})
}

const openFileSharingSettings = async (page: Page) => {
Expand Down Expand Up @@ -80,15 +89,6 @@ const confirmSharingSettings = async (
)
}

const confirmAndClosePermissionsSavedAlert = async (page: Page) => {
const permissionsAlert = page.getByRole('alert').filter({
has: page.getByText('Permissions were successfully saved to Synapse'),
})
await expect(permissionsAlert).toBeVisible()
await permissionsAlert.getByRole('button').click()
await expect(permissionsAlert).not.toBeVisible()
}

const saveFileSharingSettings = async (page: Page) => {
const saveButton = page.getByRole('button', { name: 'Save' })
await saveButton.click()
Expand All @@ -99,7 +99,62 @@ const saveFileSharingSettings = async (page: Page) => {
page.getByRole('heading', { name: 'File Sharing Settings' }),
).not.toBeVisible()

await confirmAndClosePermissionsSavedAlert(page)
await dismissAlert(page, 'Permissions were successfully saved to Synapse')
}

const uploadFile = async (
page: Page,
filePath: string,
uploadType: 'initialUpload' | 'newVersion',
) => {
await testAuth.step('open file upload modal', async () => {
const uploadButtonText =
uploadType === 'initialUpload'
? 'Upload or Link to a File'
: 'Upload a New Version of File'
await page.getByRole('button', { name: uploadButtonText }).click()
})

await testAuth.step('choose file', async () => {
const fileChooserPromise = page.waitForEvent('filechooser')
await page.getByRole('button', { name: 'Browse...' }).click()
if (uploadType === 'initialUpload') {
await page
.getByRole('menu')
.getByRole('link')
.filter({ hasText: 'Files' })
.click()
}
const fileChooser = await fileChooserPromise
await fileChooser.setFiles(path.join(__dirname, filePath))
})

await testAuth.step('wait for file upload modal to close', async () => {
await expect(page.getByText('Initializing......')).not.toBeVisible()
await expect(
page.getByRole('heading', { name: 'Upload or Link to File' }),
).not.toBeVisible()
})

await testAuth.step('ensure there was not an upload error', async () => {
await expect(
page.getByRole('heading', { name: 'Upload Error' }),
).not.toBeVisible()
})
}

const dismissFileUploadAlert = async (page: Page) => {
await testAuth.step('dismiss file upload alert', async () => {
await dismissAlert(page, 'File successfully uploaded')
})
}

const getFileMD5 = async (page: Page) => {
return await testAuth.step('get file MD5', async () => {
const row = page.getByRole('row').filter({ hasText: 'MD5' })
expect(row.getByRole('cell')).toHaveCount(2)
return row.getByRole('cell').filter({ hasNotText: 'MD5' }).textContent()
})
}

let userProject: Project
Expand Down Expand Up @@ -282,4 +337,210 @@ test.describe('Files', () => {
})
},
)

testAuth(
'should create and delete a file',
async ({ userPage, browserName }) => {
test.fail(
browserName === 'webkit',
`Playwright is not preserving the File's lastModified time in webkit, so
file upload fails because it seems like the file was modified during
upload. See https://github.com/microsoft/playwright/issues/27452. Fix
planned for Playwright v1.40.`,
)

const fileName = 'test_file.csv'
const filePath = `data/${fileName}`
const updatedFilePath = `data/test_file2.csv`

await testAuth.step('go to files tab', async () => {
await userPage.goto(entityUrlPathname(userProject.id))
await waitForInitialPageLoad(userPage)
await expect(
userPage.getByRole('heading', { name: userProject.name }),
).toBeVisible()
await userPage.getByRole('link', { name: 'Files', exact: true }).click()
})

await testAuth.step('upload file', async () => {
await uploadFile(userPage, filePath, 'initialUpload')
await dismissFileUploadAlert(userPage)
})

const fileLink = await testAuth.step('get link to file', async () => {
const fileLink = userPage.getByRole('link', { name: fileName })
await expect(fileLink).toBeVisible()
return fileLink
})

const { fileEntityId } = await testAuth.step(
'get fileEntityId',
async () => {
const fileLinkHref = await fileLink.getAttribute('href')
expect(fileLinkHref).not.toBeNull()

const fileEntityId = getEntityIdFromPathname(fileLinkHref!)
expect(fileEntityId).not.toBe('')

return { fileEntityId }
},
)

const md5v1 = await testAuth.step('view file', async () => {
await fileLink.click()
await expectFilePageLoaded(fileName, fileEntityId, userPage)
return await getFileMD5(userPage)
})

await testAuth.step('re-upload file', async () => {
await uploadFile(userPage, filePath, 'newVersion')
await expectFilePageLoaded(fileName, fileEntityId, userPage)
})

await testAuth.step(
'confirm re-upload did not change md5 or version',
async () => {
const md5reupload = await getFileMD5(userPage)
expect(md5v1).toEqual(md5reupload)
await expect(userPage.getByText('V1 (Current)')).toBeVisible()
},
)

// Upload success alert intermittently appears when re-uploading the same file
await testAuth.step(
'dismiss file upload alert for re-uploaded file, if visible',
async () => {
if (
await userPage.getByText('File successfully uploaded').isVisible()
) {
await dismissFileUploadAlert(userPage)
}
},
)

await testAuth.step('upload a new file', async () => {
await uploadFile(userPage, updatedFilePath, 'newVersion')
await expectFilePageLoaded(fileName, fileEntityId, userPage)
await dismissFileUploadAlert(userPage)
})

await testAuth.step(
'confirm uploading new file changed md5 and version',
async () => {
const md5v2 = await getFileMD5(userPage)
expect(md5v1).not.toEqual(md5v2)
await expect(userPage.getByText('V2 (Current)')).toBeVisible()
},
)

await testAuth.step(
'add associated fileHandleIds to cleanup list',
async () => {
const fileHandleIdV1 = await getEntityFileHandleId(
userPage,
getAdminPAT(),
fileEntityId,
1,
)
const fileHandleIdV2 = await getEntityFileHandleId(
userPage,
getAdminPAT(),
fileEntityId,
2,
)
fileHandleIds.push(fileHandleIdV1)
fileHandleIds.push(fileHandleIdV2)
},
)

await testAuth.step('move file to trash can', async () => {
await testAuth.step('delete file', async () => {
await userPage.getByRole('button', { name: 'File Tools' }).click()
await userPage.getByRole('menuitem', { name: 'Delete File' }).click()
})

await testAuth.step('confirm deletion', async () => {
await expect(
userPage.getByRole('heading', { name: 'Confirm Deletion' }),
).toBeVisible()
await expect(
userPage.getByText(
`Are you sure you want to delete File "${fileName}"?`,
),
).toBeVisible()
await expect(
userPage.getByRole('button', { name: 'Cancel' }),
).toBeVisible()

await userPage.getByRole('button', { name: 'Delete' }).click()
})

await testAuth.step('confirm that file was deleted', async () => {
await userPage.waitForURL(
`${entityUrlPathname(userProject.id)}/files/`,
)

await expect(
userPage.getByRole('heading', { name: 'Files' }),
).toBeVisible()
await expect(
userPage.getByRole('link', { name: fileName }),
).not.toBeVisible()

await dismissAlert(userPage, 'The File was successfully deleted.')
})
})

await testAuth.step('remove file from trash can', async () => {
await testAuth.step('go to trash can', async () => {
await userPage.getByLabel('Trash Can').click()

const trashCanHeading = userPage.getByRole('heading', {
name: 'Trash Can',
})
await expect(trashCanHeading).toBeVisible()

// click on heading, so tooltip on trash can nav button is hidden
await trashCanHeading.click()
})

await testAuth.step('remove file from trash can', async () => {
const fileCheckbox = userPage.getByRole('checkbox', {
name: `Select ${fileEntityId}`,
})
await expect(fileCheckbox).not.toBeChecked()

// Currently programmatically dispatching the click event
// because the following aren't working:
// - await fileCheckbox.click() -> fails due to <div class="pageContent margin-top-60"> intercepting pointer events
// - await fileCheckbox.click({force: true}) -> fails to actually check the checkbox
await fileCheckbox.dispatchEvent('click')

await expect(fileCheckbox).toBeChecked()

await userPage
.getByRole('button', { name: 'Delete Selected' })
.click()
})

await testAuth.step('confirm removal from trash can', async () => {
await expect(
userPage.getByText('Delete selected items from your Trash?'),
).toBeVisible()
await userPage.getByRole('button', { name: 'Delete' }).click()
})

await testAuth.step(
'confirm that file was removed from trash can',
async () => {
await expect(userPage.getByText(fileName)).not.toBeVisible()
await expect(userPage.getByText(fileEntityId)).not.toBeVisible()
await expect(
userPage.getByText('Trash Can is currently empty.'),
).toBeVisible()
},
)
})
},
)
})
24 changes: 23 additions & 1 deletion e2e/helpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@ export function generateEntityName(entityType: 'project' | 'folder' | 'file') {
return `swc-e2e-${entityType}-entity-${uuidv4()}`
}

const entityHashBang = '#!Synapse'

export function entityUrlPathname(entityId: string) {
return `/#!Synapse:${entityId}`
return `/${entityHashBang}:${entityId}`
}

export function getEntityIdFromPathname(pathname: string) {
if (!pathname.includes(entityHashBang)) {
return ''
}

return pathname
.replace(new RegExp(`.*${entityHashBang}:`), '')
.replace(/\/.*/, '')
}

export async function createProject(
Expand Down Expand Up @@ -130,6 +142,16 @@ export async function getEntity(
return entity
}

export async function getEntityFileHandleId(
page: Page,
accessToken: string | undefined = undefined,
entityId: string,
versionNumber?: string | number,
) {
const fileEntity = await getEntity(page, accessToken, entityId, versionNumber)
return fileEntity.dataFileHandleId as string
}

// https://rest-docs.synapse.org/rest/DELETE/entity/id.html
// Note: not using SRC.deleteEntity, because it does not expose the skipTrashCan option
export async function deleteEntity(
Expand Down
7 changes: 7 additions & 0 deletions e2e/helpers/testUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,10 @@ export async function getUserIdFromLocalStorage(page: Page) {
export async function acceptSiteCookies(page: Page) {
await page.getByRole('button', { name: 'Accept and continue' }).click()
}

export const dismissAlert = async (page: Page, alertText: string) => {
const alert = page.getByRole('alert').filter({ hasText: alertText })
expect(alert).toBeVisible()
await alert.getByRole('button', { name: 'Close' }).click()
await expect(alert).not.toBeVisible()
}
Loading

0 comments on commit e2c13ee

Please sign in to comment.