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

Updates to storage ui test suite #1433

Merged
merged 16 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-ternary": "^1.0.4",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"typescript": "^4.0.5",
"wsrun": "^5.2.4"
},
Expand Down
1 change: 1 addition & 0 deletions packages/files-ui/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Cypress.Commands.add(
} else {
authenticationPage.doNotSaveBrowserButton().click()
}
homePage.appHeaderLabel().should("be.visible")
})
cy.visit(url)
homePage.appHeaderLabel().should("be.visible")
Expand Down
3 changes: 1 addition & 2 deletions packages/files-ui/src/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ msgstr ""
"POT-Creation-Date: 2021-04-23 11:05+0200\n"
"PO-Revision-Date: 2021-08-06 03:33+0000\n"
"Last-Translator: J. Lavoie <j.lavoie@net-c.ca>\n"
"Language-Team: French <https://hosted.weblate.org/projects/chainsafe-files/"
"chainsafe-files-user-interface/fr/>\n"
"Language-Team: French <https://hosted.weblate.org/projects/chainsafe-files/chainsafe-files-user-interface/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
Expand Down
3 changes: 2 additions & 1 deletion packages/storage-ui/cypress.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"integrationFolder": "cypress/tests",
"video": false
"video": false,
"experimentalSessionSupport": true
}
13 changes: 1 addition & 12 deletions packages/storage-ui/cypress/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
import { existsSync, readFileSync } from "fs"

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
Expand All @@ -17,20 +16,10 @@ import { existsSync, readFileSync } from "fs"
* @type {Cypress.PluginConfig}
*/

export default (on: any) => {
export default (on: any) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config

on("task", {
readFileMaybe(filename: string) {
if (existsSync(filename)) {
return readFileSync(filename, "utf8")
}

return null
}
})

on("before:browser:launch", (browser: Cypress.Browser, launchOptions: Cypress.BrowserLaunchOptions) => {
if (browser.name === "chrome" && browser.isHeadless) {
// fullPage screenshot size is 1280x720 on non-retina screens
Expand Down
130 changes: 32 additions & 98 deletions packages/storage-ui/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,73 +27,33 @@

import { authenticationPage } from "./page-objects/authenticationPage"
import { apiTestHelper } from "./utils/apiTestHelper"
import { bucketsPage } from "./page-objects/bucketsPage"
import { ethers, Wallet } from "ethers"
import { testPrivateKey, localHost } from "../fixtures/loginData"
import { CustomizedBridge } from "./utils/CustomBridge"
import "cypress-file-upload"
import { bucketsPage } from "./page-objects/bucketsPage"
import "cypress-pipe"

export type Storage = Record<string, string>[];

export interface Web3LoginOptions {
url?: string
apiUrlBase?: string
saveBrowser?: boolean
useLocalAndSessionStorage?: boolean
clearPins?: boolean
}

const SESSION_FILE = "cypress/fixtures/storage/sessionStorage.json"
const LOCAL_FILE = "cypress/fixtures/storage/localStorage.json"

Cypress.Commands.add("clearPins", (apiUrlBase: string) => {
apiTestHelper.clearPins(apiUrlBase)
})

Cypress.Commands.add("saveLocalAndSession", () => {
// save local and session storage in files
cy.window().then((win) => {
const newLocal: Storage = []
const newSession: Storage = []

Object.keys(win.localStorage).forEach((key) => {
newLocal.push({ key, value: win.localStorage.getItem(key) || "" })
})

Object.keys(win.sessionStorage).forEach((key) => {
newSession.push({ key, value: win.sessionStorage.getItem(key) || "" })
})

const newLocalString = JSON.stringify(newLocal)
const newSessionString = JSON.stringify(newSession)

cy.writeFile(SESSION_FILE, newSessionString)
cy.writeFile(LOCAL_FILE, newLocalString)
})
})

Cypress.Commands.add(
"web3Login",
({
url = localHost,
apiUrlBase = "https://stage.imploy.site/api/v1",
useLocalAndSessionStorage = true,
clearPins = false
}: Web3LoginOptions = {}) => {
let session: Storage = []
let local: Storage = []

cy.task<string | null>("readFileMaybe", SESSION_FILE).then(
(unparsedSession) => {
session = (unparsedSession && JSON.parse(unparsedSession)) || []
}
)

cy.task<string | null>("readFileMaybe", LOCAL_FILE).then(
(unparsedLocal) => {
local = (unparsedLocal && JSON.parse(unparsedLocal)) || []
}
)

cy.on("window:before:load", (win) => {
const provider = new ethers.providers.JsonRpcProvider(
Expand All @@ -105,71 +65,38 @@ Cypress.Commands.add(
Object.defineProperty(win, "ethereum", {
get: () => new CustomizedBridge(signer as any, provider as any)
})

// clear session storage in any case, if previous session storage should be
// kept will be decided after.
// Note that Cypress keep the session storage between test but clears localStorage
win.sessionStorage.clear()
win.localStorage.clear()

if (useLocalAndSessionStorage) {
session.forEach(({ key, value }) => {
win.sessionStorage.setItem(key, value)
})

local.forEach(({ key, value }) => {
win.localStorage.setItem(key, value)
})
}
})

cy.visit(url)

// with nothing in localstorage (and in session storage)
// the whole login flow should kick in
cy.then(() => {
cy.log(
"Logging in",
local.length > 0 &&
"there is something in local storage ---> direct login"
)

if (local.length === 0) {
cy.log("nothing in local storage, --> click on web3 button")
authenticationPage.web3Button().click()
authenticationPage.showMoreButton().click()
authenticationPage.detectedWallet().click()
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
authenticationPage.web3SignInButton().click()
}
cy.session("web3login", () => {
cy.visit(url)
authenticationPage.web3Button().click()
authenticationPage.showMoreButton().click()
authenticationPage.detectedWallet().click()
authenticationPage.web3SignInButton().safeClick()
bucketsPage.bucketsHeaderLabel().should("be.visible")
Copy link
Contributor Author

@asnaith asnaith Aug 10, 2021

Choose a reason for hiding this comment

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

Just an FYI - The last line here is required so that we don't exit the session block early before we have fully logged in, prevents us exiting before the session data is created and saved.

We haven't been waiting for the header label on Files in the session block because we also have to enter a password and not save the browser so I think that gives us a little bit of extra time to save the token. To avoid any potential timing issues though I’ve added a similar check for Files too

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks, the save browser modal in Files is certainly the one that helped (just a FYI because before the password is entered we haven't reconstructed the key, and the there's prob. not much of interest in the local and session storages.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Tbaut Ah, good to know the detail. Thanks 👍

})

cy.visit(url)
bucketsPage.bucketsHeaderLabel().should("be.visible")

cy.saveLocalAndSession()

if(clearPins){
cy.clearPins(apiUrlBase)

cy.reload({ timeout: 50000 }).then(() => {
if (local.length === 0) {
// Temp work around for local storage being cleared after the reload. See issue in #1381
cy.log("nothing in local storage after reload, --> click on web3 button")
authenticationPage.web3Button().click()
authenticationPage.showMoreButton().click()
authenticationPage.detectedWallet().click()
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
authenticationPage.web3SignInButton().click()
}
})

bucketsPage.bucketsHeaderLabel().should("be.visible")
}
}
)

Cypress.Commands.add("safeClick", { prevSubject: "element" }, $element => {
const click = ($el: JQuery<HTMLElement>) => $el.trigger("click")
return cy
.wrap($element)
.should("be.visible")
.should("be.enabled")
.pipe(click)
.should($el => expect($el).to.not.be.visible)
})

// Must be declared global to be detected by typescript (allows import/export)
// eslint-disable @typescript/interface-name
declare global {
Expand All @@ -179,7 +106,6 @@ declare global {
* Login using Metamask to an instance of Storage.
* @param {String} options.url - (default: "http://localhost:3000") - what url to visit.
* @param {String} apiUrlBase - (default: "https://stage.imploy.site/api/v1") - what url to call for the api.
* @param {Boolean} options.useLocalAndSessionStorage - (default: true) - use what could have been stored before to speedup login
*/
web3Login: (options?: Web3LoginOptions) => Chainable

Expand All @@ -191,13 +117,21 @@ declare global {
clearPins: (apiUrlBase: string) => Chainable

/**
* Save local and session storage to local files
* @example cy.saveLocalAndSession()
* Use this when encountering race condition issues resulting in
* cypress "detached from DOM" issues or clicking before an event
* listener has been registered
*
* Temporary solution until cypress improve this issue
* further info
* https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/
* https://github.com/testing-library/cypress-testing-library/issues/153#issuecomment-692386444
* https://github.com/cypress-io/cypress/issues/7306
*
*/
saveLocalAndSession: () => Chainable
safeClick: () => Chainable
}
}
}

// Convert this to a module instead of script (allows import/export)
export {}
export {}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export const authenticationPage = {
web3Button: () => cy.get("[data-cy=web3]", { timeout: 120000 }),
showMoreButton: () => cy.get("div.svelte-q1527 > .bn-onboard-custom"),
detectedWallet: () => cy.get(":nth-child(3) > .bn-onboard-custom > span.svelte-1799bj2"),
web3SignInButton: () => cy.get("[data-cy=sign-in-with-web3-button]")
web3SignInButton: () => cy.get("[data-cy=sign-in-with-web3-button]", { timeout: 10000 })
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ export const bucketsPage = {
bucketItemRow: () => cy.get("[data-cy=row-bucket-item]", { timeout: 20000 }),
nameTableHeader: () => cy.get("[data-cy=table-header-name]"),
sizeTableHeader: () => cy.get("[data-cy=table-header-size]"),
bucketItemName: () => cy.get("[data-cy=cell-bucket-name]"),
bucketRowKebabButton: () => cy.get("[data-testid=dropdown-title-bucket-kebab]"),

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

// menu elements
deleteBucketMenuOption: () => cy.get("[data-cy=menu-delete-bucket]"),
Expand All @@ -28,7 +29,7 @@ export const bucketsPage = {
createBucket(bucketName: string) {
this.createBucketButton().click()
this.bucketNameInput().type(bucketName)
this.createBucketSubmitButton().click()
this.createBucketSubmitButton().safeClick()
this.createBucketForm().should("not.exist")
}
}
6 changes: 4 additions & 2 deletions packages/storage-ui/cypress/tests/bucket-management-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ describe("Bucket management", () => {
navigationMenu.bucketsNavButton().click()
bucketsPage.createBucketButton().click()
bucketsPage.bucketNameInput().type(bucketName)
bucketsPage.createBucketSubmitButton().click()
bucketsPage.createBucketSubmitButton().safeClick()
bucketsPage.bucketItemRow().should("have.length", 1)
bucketsPage.bucketItemName().should("have.text", bucketName)

// open create bucket modal and cancel it
bucketsPage.createBucketButton().click()
Expand All @@ -31,7 +32,8 @@ describe("Bucket management", () => {
bucketsPage.createBucket(bucketName)
bucketsPage.bucketRowKebabButton().first().click()
bucketsPage.deleteBucketMenuOption().first().click()
bucketsPage.bucketItemRow().should("not.exist")
bucketsPage.bucketItemRow().should("not.be.visible")
bucketsPage.bucketItemName().should("not.be.visible")
})
})
})
4 changes: 3 additions & 1 deletion packages/storage-ui/cypress/tests/cid-management-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cidsPage } from "../support/page-objects/cidsPage"
import { navigationMenu } from "../support/page-objects/navigationMenu"
import { testCid } from "../fixtures/storageTestData"

describe("CID management", () => {
Expand All @@ -7,11 +8,12 @@ describe("CID management", () => {

it.skip("can pin a CID", () => {
cy.web3Login({ clearPins: true })
navigationMenu.cidsNavButton().click()

// pin a cid and see it in the pinned items table
cidsPage.pinButton().click()
cidsPage.cidInput().type(testCid)
cidsPage.pinSubmitButton().click()
cidsPage.pinSubmitButton().safeClick()
cidsPage.cidItemRow().should("have.length", 1)

// open the pin cid modal and cancel it
Expand Down
5 changes: 2 additions & 3 deletions packages/storage-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@
"build": "craco --max_old_space_size=4096 build",
"sentry": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); node scripts/sentry.js)",
"release": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); yarn compile && yarn build && node scripts/sentry.js)",
"test": "yarn test:clean && cypress open",
"test:ci": "yarn test:clean && cypress run --browser chrome --headless",
"test:clean": "rimraf cypress/fixtures/storage",
"test": "cypress open",
"test:ci": "cypress run --browser chrome --headless",
"analyze": "source-map-explorer 'build/static/js/*.js'",
"extract": "lingui extract",
"compile": "lingui compile",
Expand Down
4 changes: 3 additions & 1 deletion packages/storage-ui/src/Components/Elements/BucketRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ const BucketRow = ({ bucket }: Props) => {
})}
data-cy="row-bucket-item"
>
<TableCell className={classes.name}
<TableCell
className={classes.name}
data-cy="cell-bucket-name"
onClick={() => redirect(ROUTE_LINKS.Bucket(bucket.id, "/"))}>
{bucket.name || bucket.id}
</TableCell>
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20299,7 +20299,7 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"

rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
rimraf@3.0.2, rimraf@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
Expand Down