mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-15 19:33:32 +00:00
adding cypress tests
This commit is contained in:
cypress.config.js
cypress
e2e
fixtures
support
frontend
package-lock.jsonpackage.json
src
components/v2/DeleteActionModal
views
SecretMainPage
SecretMainPage.tsx
components
Settings
OrgSettingsPage/components/OrgGeneralTab
ProjectSettingsPage/components/DeleteProjectSection
7
cypress.config.js
Normal file
7
cypress.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:8080',
|
||||
viewportWidth: 1480,
|
||||
viewportHeight: 920,
|
||||
},
|
||||
};
|
47
cypress/e2e/org-overview.cy.js
Normal file
47
cypress/e2e/org-overview.cy.js
Normal file
@ -0,0 +1,47 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('organization Overview', () => {
|
||||
beforeEach(() => {
|
||||
cy.login(`test@localhost.local`, `testInfisical1`)
|
||||
})
|
||||
|
||||
const projectName = "projectY"
|
||||
|
||||
it('can`t create projects with empty names', () => {
|
||||
cy.get('.button').click()
|
||||
cy.get('input[placeholder="Type your project name"]').type('abc').clear()
|
||||
cy.intercept('*').as('anyRequest');
|
||||
cy.get('@anyRequest').should('not.exist');
|
||||
})
|
||||
|
||||
it('can delete a newly-created project', () => {
|
||||
// Create a project
|
||||
cy.get('.button').click()
|
||||
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
|
||||
cy.contains('button', 'Create Project').click()
|
||||
cy.url().should('include', '/project')
|
||||
|
||||
// Delete a project
|
||||
cy.get(`[href^="/project/"][href$="/settings"] > a > .group`).click()
|
||||
cy.contains('button', `Delete ${projectName}`).click()
|
||||
cy.contains('button', 'Delete Project').should('have.attr', 'disabled')
|
||||
cy.get('input[placeholder="Type to delete..."]').type('confirm')
|
||||
cy.contains('button', 'Delete Project').should('not.have.attr', 'disabled')
|
||||
cy.url().then((currentUrl) => {
|
||||
let projectId = currentUrl.split("/")[4]
|
||||
cy.intercept('DELETE', `/api/v1/workspace/${projectId}`).as('deleteProject');
|
||||
cy.contains('button', 'Delete Project').click();
|
||||
cy.get('@deleteProject').should('have.property', 'response').and('have.property', 'statusCode', 200);
|
||||
})
|
||||
})
|
||||
|
||||
it('can display no projects', () => {
|
||||
cy.intercept('/api/v1/workspace', {
|
||||
body: {
|
||||
"workspaces": []
|
||||
},
|
||||
})
|
||||
cy.get('.border-mineshaft-700 > :nth-child(2)').should('have.text', 'You are not part of any projects in this organization yet. When you are, they will appear here.')
|
||||
})
|
||||
|
||||
})
|
24
cypress/e2e/org-settings.cy.js
Normal file
24
cypress/e2e/org-settings.cy.js
Normal file
@ -0,0 +1,24 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('Organization Settings', () => {
|
||||
let orgId;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login(`test@localhost.local`, `testInfisical1`)
|
||||
cy.url().then((currentUrl) => {
|
||||
orgId = currentUrl.split("/")[4]
|
||||
cy.visit(`org/${orgId}/settings`)
|
||||
})
|
||||
})
|
||||
|
||||
it('can rename org', () => {
|
||||
cy.get('input[placeholder="Acme Corp"]').clear().type('ABC')
|
||||
|
||||
cy.intercept('PATCH', `/api/v1/organization/${orgId}/name`).as('renameOrg');
|
||||
cy.get('form.p-4 > .button').click()
|
||||
cy.get('@renameOrg').should('have.property', 'response').and('have.property', 'statusCode', 200);
|
||||
|
||||
cy.get('.pl-3').should("have.text", "ABC ")
|
||||
})
|
||||
|
||||
})
|
84
cypress/e2e/project-overview.cy.js
Normal file
84
cypress/e2e/project-overview.cy.js
Normal file
@ -0,0 +1,84 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('Project Overview', () => {
|
||||
const projectName = "projectY"
|
||||
let projectId;
|
||||
let isFirstTest = true;
|
||||
|
||||
before(() => {
|
||||
cy.login(`test@localhost.local`, `testInfisical1`)
|
||||
|
||||
// Create a project
|
||||
cy.get('.button').click()
|
||||
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
|
||||
cy.contains('button', 'Create Project').click()
|
||||
cy.url().should('include', '/project').then((currentUrl) => {
|
||||
projectId = currentUrl.split("/")[4]
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
if (isFirstTest) {
|
||||
isFirstTest = false;
|
||||
return; // Skip the rest of the beforeEach for the first test
|
||||
}
|
||||
cy.login(`test@localhost.local`, `testInfisical1`)
|
||||
cy.visit(`/project/${projectId}/secrets/overview`)
|
||||
})
|
||||
|
||||
it('can create secrets', () => {
|
||||
cy.contains('button', 'Go to Development').click()
|
||||
cy.contains('button', 'Add a new secret').click()
|
||||
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
|
||||
cy.contains('button', 'Create Secret').click()
|
||||
cy.get('.w-80 > .inline-flex > .input').should('have.value', 'SECRET_A')
|
||||
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '1 Commit')
|
||||
})
|
||||
|
||||
it('can update secrets', () => {
|
||||
cy.get(':nth-child(2) > .flex > .button').click()
|
||||
cy.get('.overflow-auto > .relative > .absolute').type('VALUE_A')
|
||||
cy.get('.button.text-primary > .svg-inline--fa').click()
|
||||
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '2 Commits')
|
||||
})
|
||||
|
||||
it('can`t create duplicate-name secrets', () => {
|
||||
cy.get(':nth-child(2) > .flex > .button').click()
|
||||
cy.contains('button', 'Add Secret').click()
|
||||
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
|
||||
cy.intercept('POST', `/api/v3/secrets/SECRET_A`).as('createSecret');
|
||||
cy.contains('button', 'Create Secret').click()
|
||||
cy.get('@createSecret').should('have.property', 'response').and('have.property', 'statusCode', 400);
|
||||
})
|
||||
|
||||
it('can add another secret', () => {
|
||||
cy.get(':nth-child(2) > .flex > .button').click()
|
||||
cy.contains('button', 'Add Secret').click()
|
||||
cy.get('input[placeholder="Type your secret name"]').type('SECRET_B')
|
||||
cy.contains('button', 'Create Secret').click()
|
||||
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '3 Commits')
|
||||
})
|
||||
|
||||
it('can delete a secret', () => {
|
||||
cy.get(':nth-child(2) > .flex > .button').click()
|
||||
// cy.get(':nth-child(3) > .shadow-none').trigger('mouseover')
|
||||
cy.get(':nth-child(3) > .shadow-none > .group > .h-10 > .border-red').click()
|
||||
cy.contains('button', 'Delete Secret').should('have.attr', 'disabled')
|
||||
cy.get('input[placeholder="Type to delete..."]').type('SECRET_B')
|
||||
cy.intercept('DELETE', `/api/v3/secrets/SECRET_B`).as('deleteSecret');
|
||||
cy.contains('button', 'Delete Secret').should('not.have.attr', 'disabled')
|
||||
cy.contains('button', 'Delete Secret').click();
|
||||
cy.get('@deleteSecret').should('have.property', 'response').and('have.property', 'statusCode', 200);
|
||||
})
|
||||
|
||||
it('can add a comment', () => {
|
||||
return;
|
||||
cy.get(':nth-child(2) > .flex > .button').click()
|
||||
// for some reason this hover does not want to work
|
||||
cy.get('.overflow-auto').trigger('mouseover').then(() => {
|
||||
cy.get('.shadow-none > .group > .pl-4 > .h-8 > button[aria-label="add-comment"]').should('be.visible').click()
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
})
|
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
51
cypress/support/commands.js
Normal file
51
cypress/support/commands.js
Normal file
@ -0,0 +1,51 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('login', (username, password) => {
|
||||
cy.visit('/login')
|
||||
cy.get('input[placeholder="Enter your email..."]').type(username)
|
||||
cy.get('input[placeholder="Enter your password..."]').type(password)
|
||||
cy.contains('Continue with Email').click()
|
||||
cy.url().should('include', '/overview')
|
||||
|
||||
// Need to make this work for CSRF tokens; Cypress has an example in the docs
|
||||
// cy.session(
|
||||
// username,
|
||||
// () => {
|
||||
// cy.visit('/login')
|
||||
// cy.get('input[placeholder="Enter your email..."]').type(username)
|
||||
// cy.get('input[placeholder="Enter your password..."]').type(password)
|
||||
// cy.contains('Continue with Email').click()
|
||||
// cy.url().should('include', '/overview')
|
||||
// },
|
||||
// {
|
||||
// validate: () => {
|
||||
// cy.getCookie('jid').should('exist')
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
})
|
||||
|
20
cypress/support/e2e.js
Normal file
20
cypress/support/e2e.js
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
1920
frontend/package-lock.json
generated
1920
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -124,6 +124,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"cypress": "^13.3.2",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
|
@ -15,6 +15,7 @@ type Props = {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
onDeleteApproved: () => Promise<void>;
|
||||
buttonText?: string;
|
||||
};
|
||||
|
||||
export const DeleteActionModal = ({
|
||||
@ -24,7 +25,8 @@ export const DeleteActionModal = ({
|
||||
deleteKey,
|
||||
onDeleteApproved,
|
||||
title,
|
||||
subTitle = "This action is irreversible!"
|
||||
subTitle = "This action is irreversible!",
|
||||
buttonText = "Delete"
|
||||
}: Props): JSX.Element => {
|
||||
const [inputData, setInputData] = useState("");
|
||||
const [isLoading, setIsLoading] = useToggle();
|
||||
@ -56,7 +58,7 @@ export const DeleteActionModal = ({
|
||||
title={title}
|
||||
subTitle={subTitle}
|
||||
footerContent={
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center mx-2">
|
||||
<Button
|
||||
className="mr-4"
|
||||
colorSchema="danger"
|
||||
@ -64,7 +66,7 @@ export const DeleteActionModal = ({
|
||||
onClick={onDelete}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Delete
|
||||
{buttonText}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
|
||||
@ -87,9 +89,9 @@ export const DeleteActionModal = ({
|
||||
Type <span className="font-bold">{deleteKey}</span> to delete the resource
|
||||
</div>
|
||||
}
|
||||
className="mb-4"
|
||||
className="mb-0"
|
||||
>
|
||||
<Input value={inputData} onChange={(e) => setInputData(e.target.value)} />
|
||||
<Input value={inputData} onChange={(e) => setInputData(e.target.value)} placeholder="Type to delete..." />
|
||||
</FormControl>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
@ -239,7 +239,7 @@ export const SecretMainPage = () => {
|
||||
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
||||
/>
|
||||
<div className="mt-3 overflow-y-auto overflow-x-hidden thin-scrollbar bg-mineshaft-800 text-left text-bunker-300 rounded-md text-sm">
|
||||
<div className="flex flex-col ">
|
||||
<div className="flex flex-col" id="dashboard">
|
||||
{isNotEmtpy && (
|
||||
<div className="flex font-medium border-b border-mineshaft-600">
|
||||
<div style={{ width: "2.8rem" }} className="px-4 py-3 flex-shrink-0" />
|
||||
|
@ -289,7 +289,7 @@ export const ActionBar = ({
|
||||
className="h-10"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
{snapshotCount} Commits
|
||||
{`${snapshotCount} ${snapshotCount === 1 ? "Commit" : "Commits"}`}
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
@ -105,7 +105,7 @@ export const CreateSecretForm = ({
|
||||
>
|
||||
<SecretInput
|
||||
{...field}
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
@ -339,6 +339,7 @@ export const SecretListView = ({
|
||||
title="Do you want to delete this secret?"
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSecret", isOpen)}
|
||||
onDeleteApproved={handleSecretDelete}
|
||||
buttonText="Delete Secret"
|
||||
/>
|
||||
<SecretDetailSidebar
|
||||
environment={environment}
|
||||
|
@ -10,7 +10,7 @@ export const OrgGeneralTab = () => {
|
||||
const { user } = useUser();
|
||||
const { data: members } = useGetOrgUsers(currentOrg?._id ?? "");
|
||||
|
||||
const membershipOrg = members?.find((member) => member.user._id === user._id);
|
||||
const membershipOrg = members?.find((member) => member.user._id === user?._id);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -77,9 +77,10 @@ export const DeleteProjectSection = () => {
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteWorkspace.isOpen}
|
||||
title="Are you sure want to delete this project?"
|
||||
subTitle={`Permanently remove ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Delete Project"
|
||||
onDeleteApproved={handleDeleteWorkspaceSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user