Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve bucket upload ui tests #2117

Merged
merged 15 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ interface IFormikRadioInputProps extends React.HTMLProps<HTMLInputElement> {
name: string
label?: string
id: string
testId: string
}

const FormikRadioInput: React.FC<IFormikRadioInputProps> = ({
name,
onChange,
id,
label,
testId,
...props
}) => {
const [field, meta, helpers] = useField<string>(name)
Expand All @@ -32,6 +34,7 @@ const FormikRadioInput: React.FC<IFormikRadioInputProps> = ({
value={id}
checked={field.value === id}
label={label}
testId={testId}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const fileUploadModal = {
body: () => cy.get("[data-cy=form-upload-file] input", { timeout: 20000 }),
body: () => cy.get("[data-cy=form-upload-file] input", { timeout: 10000 }),
cancelButton: () => cy.get("[data-testid=button-cancel-upload]"),
fileList: () => cy.get("[data-testid=list-fileUpload] li"),
removeFileButton: () => cy.get("[data-testid=button-remove-from-file-list]"),
uploadButton: () => cy.get("[data-testid=button-start-upload]", { timeout: 10000 }),
uploadButton: () => cy.get("[data-testid=button-start-upload]"),
uploadDropzone : () => cy.get("[data-testid=input-file-dropzone-fileUpload]"),
errorLabel: () => cy.get("[data-testid=meta-error-message-fileUpload]"),

Expand Down
5 changes: 3 additions & 2 deletions packages/storage-ui/cypress/fixtures/storageTestData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const bucketName = "test bucket"
export const chainSafeBucketName = `cs bucket ${Date.now()}`
export const ipfsBucketName = `ipfs bucket ${Date.now()}`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We remove the bucket from the UI instantly and don't wait for any confirmation so to avoid issues I'm using unique names here.

export const testCid = "QmZEE7Ymh2mRMURLnFLipJTovb44AoUYDzx7aipYZwvxX5"
export const testCidAlternative = "QmSMNExVSNKwWNeXL3o6a2og13441g1eUqLgTeuCSDvN2W"
export const testCidName = "cute cat"
export const testCidName = "cute cat"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { basePage } from "./basePage"

export const bucketContentsPage = {
...basePage,

// bucket content browser elements
bucketHeaderLabel: () => cy.get("[data-cy=header-bucket]", { timeout: 20000 }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this timeout is because this type of web elements take longer to load than usual, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually...this was unnecessary 😄. It was left there by accident. I have pushed a commit to remove it and some others which aren't needed.

The default is 6 seconds and that will be ok.

newFolderButton: () => cy.get("[data-testid=button-new-folder] "),
uploadButton: () => cy.get("[data-testid=button-upload-file]"),

// file or folder browser row elements
fileItemKebabButton: () => cy.get("[data-testid=icon-file-item-kebab]", { timeout: 10000 }),
fileItemName: () => cy.get("[data-cy=label-file-item-name]"),
fileItemRow: () => cy.get("[data-cy=row-file-item]", { timeout: 20000 }),

// kebab menu elements
downloadMenuOption: () => cy.get("[data-cy=menu-download]"),
renameMenuOption: () => cy.get("[data-cy=menu-rename]"),
moveMenuOption: () => cy.get("[data-cy=menu-move]"),
deleteMenuOption: () => cy.get("[data-cy=menu-delete]"),

// helpers and convenience functions
awaitBucketRefresh() {
cy.intercept("POST", "**/bucket/*/ls").as("refresh")
cy.wait("@refresh")
}
}
17 changes: 7 additions & 10 deletions packages/storage-ui/cypress/support/page-objects/bucketsPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Only add things here that could be applicable to the bucket page

import { basePage } from "./basePage"
import { createBucketModal } from "./modals/createBucketModal"

export const bucketsPage = {
...basePage,
Expand All @@ -14,22 +15,18 @@ export const bucketsPage = {
nameTableHeader: () => cy.get("[data-cy=table-header-name]"),
sizeTableHeader: () => cy.get("[data-cy=table-header-size]"),
bucketItemName: () => cy.get("[data-cy=cell-bucket-name]"),
bucketFileSystemType: () => cy.get("[data-cy=cell-file-system-type]"),
bucketRowKebabButton: () => cy.get("[data-testid=dropdown-title-bucket-kebab]", { timeout: 10000 }),

// create bucket modal elements
createBucketForm: () => cy.get("[data-testid=form-create-bucket]", { timeout: 10000 }),
bucketNameInput: () => cy.get("[data-cy=input-bucket-name]", { timeout: 10000 }),
createBucketCancelButton: () => cy.get("[data-cy=button-cancel-create]"),
createBucketSubmitButton: () => cy.get("[data-cy=button-submit-create]", { timeout: 10000 }),

// menu elements
deleteBucketMenuOption: () => cy.get("[data-cy=menu-delete-bucket]"),

// helpers and convenience functions
createBucket(bucketName: string) {
this.createBucketButton().click()
this.bucketNameInput().type(bucketName)
this.createBucketSubmitButton().safeClick()
this.createBucketForm().should("not.exist")
createBucketModal.body().should("be.visible")
createBucketModal.bucketNameInput().type(bucketName)
createBucketModal.submitButton().safeClick()
createBucketModal.body().should("not.exist")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const createBucketModal = {
body: () => cy.get("[data-testid=modal-container-create-bucket]", { timeout: 10000 }),
bucketNameInput: () => cy.get("[data-cy=input-bucket-name]"),
chainsafeRadioInput: () => cy.get("[data-testid=radio-input-chainsafe]"),
ipfsRadioInput: () => cy.get("[data-testid=radio-input-ipfs]"),
cancelButton: () => cy.get("[data-cy=button-cancel-create]"),
submitButton: () => cy.get("[data-cy=button-submit-create]")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const fileUploadModal = {
body: () => cy.get("[data-cy=form-upload-file] input", { timeout: 10000 }),
cancelButton: () => cy.get("[data-testid=button-cancel-upload]"),
fileList: () => cy.get("[data-testid=list-fileUpload] li"),
removeFileButton: () => cy.get("[data-testid=button-remove-from-file-list]"),
uploadButton: () => cy.get("[data-testid=button-start-upload]"),
uploadDropzone : () => cy.get("[data-testid=input-file-dropzone-fileUpload]"),
errorLabel: () => cy.get("[data-testid=meta-error-message-fileUpload]")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const uploadStatusToast = {
body: () => cy.get("[data-cy=upload_status_toast_message]", { timeout: 20000 })
}
92 changes: 75 additions & 17 deletions packages/storage-ui/cypress/tests/bucket-management-spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,96 @@
import { bucketsPage } from "../support/page-objects/bucketsPage"
import { bucketName } from "../fixtures/storageTestData"

import { bucketContentsPage } from "../support/page-objects/bucketContentsPage"
import { chainSafeBucketName, ipfsBucketName } from "../fixtures/storageTestData"
import { createBucketModal } from "../support/page-objects/modals/createBucketModal"
import { navigationMenu } from "../support/page-objects/navigationMenu"
import { fileUploadModal } from "../support/page-objects/modals/fileUploadModal"
import { uploadStatusToast } from "../support/page-objects/toasts/uploadStatusToast"

describe("Bucket management", () => {

context("desktop", () => {

it("can create a bucket", () => {
it("can create, upload file and delete a chainsafe bucket", () => {
cy.web3Login({ clearPins: true, deleteFpsBuckets: true })
navigationMenu.bucketsNavButton().click()

// open create bucket modal and cancel it
bucketsPage.createBucketButton().click()
createBucketModal.cancelButton().click()
createBucketModal.body().should("not.exist")

// create a bucket and see it in the bucket table
navigationMenu.bucketsNavButton().click()
bucketsPage.createBucketButton().click()
bucketsPage.bucketNameInput().type(bucketName)
bucketsPage.createBucketSubmitButton().safeClick()
createBucketModal.body().should("be.visible")
createBucketModal.bucketNameInput().type(chainSafeBucketName)
createBucketModal.chainsafeRadioInput().click()
createBucketModal.submitButton().click()
bucketsPage.bucketItemRow().should("have.length", 1)
bucketsPage.bucketItemName().should("have.text", bucketName)
bucketsPage.bucketItemName().should("have.text", chainSafeBucketName)
bucketsPage.bucketFileSystemType().should("have.text", "Chainsafe")

// open create bucket modal and cancel it
bucketsPage.createBucketButton().click()
bucketsPage.createBucketCancelButton().click()
bucketsPage.createBucketForm().should("not.exist")
// open bucket and ensure header matches the expected value
bucketsPage.bucketItemName().dblclick()
bucketContentsPage.bucketHeaderLabel()
.should("be.visible")
.should("contain.text", chainSafeBucketName)

// upload a file to the bucket
bucketContentsPage.uploadButton().click()
fileUploadModal.body().attachFile("../fixtures/uploadedFiles/logo.png")
fileUploadModal.fileList().should("have.length", 1)
fileUploadModal.uploadButton().safeClick()
fileUploadModal.body().should("not.exist")
uploadStatusToast.body().should("be.visible")
bucketContentsPage.awaitBucketRefresh()
bucketContentsPage.fileItemRow().should("have.length", 1)

// delete chainsafe bucket
navigationMenu.bucketsNavButton().click()
bucketsPage.bucketRowKebabButton()
.should("be.visible")
.click()
bucketsPage.deleteBucketMenuOption().click()
bucketsPage.bucketItemRow().should("not.exist")
bucketsPage.bucketItemName().should("not.exist")
})

it("can delete a bucket", () => {
it("can create, upload file and delete an ipfs bucket", () => {
cy.web3Login({ clearPins: true, deleteFpsBuckets: true })
navigationMenu.bucketsNavButton().click()

// create a bucket and see it in the bucket table
bucketsPage.createBucketButton().click()
createBucketModal.body().should("be.visible")
createBucketModal.bucketNameInput().type(ipfsBucketName)
createBucketModal.ipfsRadioInput().click()
createBucketModal.submitButton().click()
bucketsPage.bucketItemRow().should("have.length", 1)
bucketsPage.bucketItemName().should("have.text", ipfsBucketName)
bucketsPage.bucketFileSystemType().should("have.text", "IPFS MFS")

// open bucket and ensure header matches the expected value
bucketsPage.bucketItemName().dblclick()
bucketContentsPage.bucketHeaderLabel()
.should("be.visible")
.should("contain.text", ipfsBucketName)

// upload a file to the bucket
bucketContentsPage.uploadButton().click()
fileUploadModal.body().attachFile("../fixtures/uploadedFiles/logo.png")
fileUploadModal.fileList().should("have.length", 1)
fileUploadModal.uploadButton().safeClick()
fileUploadModal.body().should("not.exist")
uploadStatusToast.body().should("be.visible")
bucketContentsPage.awaitBucketRefresh()
bucketContentsPage.fileItemRow().should("have.length", 1)

// delete a bucket and ensure its row is removed
// delete ipfs bucket
navigationMenu.bucketsNavButton().click()
// creating a bucket with a unique name
bucketsPage.createBucket(`${bucketName}_${Date.now()}`)
bucketsPage.bucketRowKebabButton().first().click()
bucketsPage.deleteBucketMenuOption().first().click()
bucketsPage.bucketRowKebabButton()
.should("be.visible")
.click()
bucketsPage.deleteBucketMenuOption().click()
bucketsPage.bucketItemRow().should("not.exist")
bucketsPage.bucketItemName().should("not.exist")
})
Expand Down
3 changes: 2 additions & 1 deletion packages/storage-ui/src/Components/Elements/BucketRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ const BucketRow = ({ bucket }: Props) => {
onClick={() => redirect(ROUTE_LINKS.Bucket(bucket.id, "/"))}>
{bucket.name || bucket.id}
</TableCell>
<TableCell>
<TableCell
data-cy="cell-file-system-type">
{bucket.file_system_type === "ipfs" ? "IPFS MFS" : "Chainsafe" }
</TableCell>
<TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ const FilesList = () => {
</div>
<header
className={classes.header}
data-cy="header-bucket-item"
data-cy="header-bucket"
>
<Typography
variant="h1"
Expand All @@ -684,23 +684,23 @@ const FilesList = () => {
{controls && desktop ? (
<>
<Button
data-cy="button-new-folder"
onClick={() => setCreateFolderModalOpen(true)}
variant="outline"
size="large"
disabled={accountRestricted}
testId="new-folder"
>
<PlusCircleIcon />
<span className={classes.buttonWrap}>
<Trans>New folder</Trans>
</span>
</Button>
<Button
data-cy="button-bucket-upload"
onClick={() => setIsUploadModalOpen(true)}
variant="outline"
size="large"
disabled={accountRestricted}
testId="upload-file"
>
<UploadIcon />
<span className={classes.buttonWrap}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const UploadFileModal = ({ modalOpen, close }: IUploadFileModuleProps) => {
/>
<footer className={classes.footer}>
<Button
data-cy="upload-cancel-button"
testId="cancel-upload"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why testId and not data-cy? this modal es reusable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juans-chainsafe In this example specifically, the button component already has a testId prop so I'm just passing something through to that, as opposed to writing a custom attribute directly on the element. It's a little cleaner / more readable ("testId" is instantly obvious to all reading the code what it's for).

Screen Shot 2022-05-04 at 10 06 30 AM

For buttons, either type of locator will work, but there are instances with other components where this is not the case:

  • Due to the manner in which we are setting up the element in code (maybe using the default component without further customizations) it's sometimes hard to add a custom data-cy attribute to it (off the top of my head I think toasts in Files is a good example).

  • I've also seen instances where even though we have set up a data-cy tag Cypress hasn't been able to find it but by setting up a testId on the component and passing something through to it does work (saw this recently with radio buttons).

I am certain you will encounter this too at some point. Hopefully, this explanation makes sense but we can also discuss it further if you want :)

onClick={close}
size="medium"
className={classes.cancelButton}
Expand All @@ -146,7 +146,7 @@ const UploadFileModal = ({ modalOpen, close }: IUploadFileModuleProps) => {
<Trans>Cancel</Trans>
</Button>
<Button
data-cy="upload-ok-button"
testId="start-upload"
size="medium"
type="submit"
variant="primary"
Expand Down
4 changes: 3 additions & 1 deletion packages/storage-ui/src/Components/Pages/BucketsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ const BucketsPage = () => {
inner: classes.modalInner
}}
closePosition="none"
testId="create-bucket"
>
<div
className={classes.modalRoot}
data-cy="form-create-bucket"
>
<FormikProvider value={formik}>
<Form>
Expand Down Expand Up @@ -282,11 +282,13 @@ const BucketsPage = () => {
name="fileSystemType"
id='chainsafe'
label='Chainsafe'
testId="chainsafe"
/>
<FormikRadioInput
name="fileSystemType"
id='ipfs'
label='IPFS'
testId="ipfs"
/>
</div>
</label>
Expand Down