Skip to content

Commit

Permalink
merge: #3477
Browse files Browse the repository at this point in the history
3477: feat: add target for cypress e2e tests into BUCK r=johnrwatson a=johnrwatson

This PR adds a new toolchain into our BUCK build system to allow us to execute Cypress end to end tests via BUCK directly.

In a follow up PR, the test will then be executed in CI.

The added target is runnable and buildable, depending on how you would like to execute it.

You can explicitly invoke as follows:
`buck2 run app/web:e2e-test -- --tests 'cypress/e2e/**' --web-endpoint http://localhost:8080`

This will run all our e2e tests against your local stack. These values are the defaults and can be excluded entirely on invocation if you desire, i.e.
`buck2 run app/web:e2e-test`

When executed, the target will check a few things before executing, these are listed below:
- The stack is responsive on the given `--web-endpoint` within 60s
- Cypress installation is valid
- You are on an supported OS / Architecture

During execution, it will then output the log contents and videos from cypress into the BUCK out path, which is usually something in the form of:
`si/buck-out/v2/gen/root/<uuid>/app/web/__e2e-test__/<content here>`

**Variables**
Some variables are required to allow these tests to execute successfully and each test has slightly differing requirements. In the header of each of the cypress typescript test files you will see a block like follows:
```
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_API_URL = Cypress.env('VITE_AUTH_API_URL') || import.meta.env.VITE_AUTH_API_URL;
const SI_WORKSPACE_ID = Cypress.env('VITE_SI_WORKSPACE_ID') || import.meta.env.VITE_SI_WORKSPACE_ID;
```

This basically means, either have the variables in your local shell environment or within the .env files for vite, i.e. `.env.local` usually. I've included some psuedocode in `app/web/cypress/support/commands.ts` as to how we might refactor this to centralise the variables in one place, but I got into a bit of a tizzy with cypress coinage so I may have to come back to this in a follow up PR to implement the new test into CI.



Co-authored-by: John Watson <[email protected]>
  • Loading branch information
si-bors-ng[bot] and johnrwatson authored Mar 27, 2024
2 parents 4f948a9 + cc06b14 commit ab23fd7
Show file tree
Hide file tree
Showing 21 changed files with 562 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ vendor/automerge
/support/dev/k8s/**/web-htpasswd
/tmp/
deploy/docker-compose.env.yml
app/web/cypress/downloads/
app/web/cypress/screenshots/
app/web/cypress/videos/
bin/si/src/version.txt
Expand Down
5 changes: 5 additions & 0 deletions app/web/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ load(
"@prelude-si//:macros.bzl",
"docker_image",
"eslint",
"e2e_test",
"export_file",
"package_node_modules",
"shellcheck",
Expand Down Expand Up @@ -167,3 +168,7 @@ pnpm_task_test(
path = "app/web",
visibility = ["PUBLIC"],
)

e2e_test(
name = "e2e-test",
)
18 changes: 13 additions & 5 deletions app/web/cypress/e2e/authentication/login.cy.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VITE_SI_CYPRESS_MULTIPLIER : 1, () => {
const SI_CYPRESS_MULTIPLIER = Cypress.env('VITE_SI_CYPRESS_MULTIPLIER') || import.meta.env.VITE_SI_CYPRESS_MULTIPLIER || 1;
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_API_URL = Cypress.env('VITE_AUTH_API_URL') || import.meta.env.VITE_AUTH_API_URL;
const SI_WORKSPACE_ID = Cypress.env('VITE_SI_WORKSPACE_ID') || import.meta.env.VITE_SI_WORKSPACE_ID;
const UUID = Cypress.env('VITE_UUID') || import.meta.env.VITE_UUID || "local";

Cypress._.times(SI_CYPRESS_MULTIPLIER, () => {
describe("login", () => {
beforeEach(() => {
cy.visit("/");
});

it("log_in", () => {
cy.loginToAuth0(import.meta.env.VITE_AUTH0_USERNAME, import.meta.env.VITE_AUTH0_PASSWORD);
cy.visit("/");
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.loginToAuth0(AUTH0_USERNAME, AUTH0_PASSWORD);
cy.visit(AUTH_API_URL + '/workspaces/' + SI_WORKSPACE_ID + '/go');
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", UUID);
// check that you're on head i.e. that you were redirected correctly
cy.url().should("contain", "head");
cy.wait(4000)
cy.url().should("contain", SI_WORKSPACE_ID);
});

});
Expand Down
20 changes: 13 additions & 7 deletions app/web/cypress/e2e/authentication/logout.cy.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VITE_SI_CYPRESS_MULTIPLIER : 1, () => {
const SI_CYPRESS_MULTIPLIER = Cypress.env('VITE_SI_CYPRESS_MULTIPLIER') || import.meta.env.VITE_SI_CYPRESS_MULTIPLIER || 1;
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_PORTAL_URL = Cypress.env('VITE_AUTH_PORTAL_URL') || import.meta.env.VITE_AUTH_PORTAL_URL;
const UUID = Cypress.env('VITE_UUID') || import.meta.env.VITE_UUID || "local";

Cypress._.times(SI_CYPRESS_MULTIPLIER, () => {
describe("logout", () => {
beforeEach(() => {
cy.visit("/");
cy.loginToAuth0(import.meta.env.VITE_AUTH0_USERNAME, import.meta.env.VITE_AUTH0_PASSWORD);
cy.loginToAuth0(AUTH0_USERNAME, AUTH0_PASSWORD);
});

it("log_out", () => {
cy.visit("/");
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.contains('Create change set', { timeout: 10000 }).should('be.visible').click();
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", UUID);
cy.contains('Create change set', { timeout: 10000 }).should('be.visible');
cy.get('.modal-close-button').should('exist').click();
cy.get('[aria-label="Profile"]').should('exist').click();
cy.get('#dropdown-menu-item-1').should('exist').should('be.visible').click({ force: true });

// There is a bug currently where you log out of the product & it just logs you out to the dashboard page
// of the UI in auth portal
cy.url().should("contain", import.meta.env.VITE_AUTH_PORTAL_URL + '/dashboard');

cy.url().should("contain", AUTH_PORTAL_URL + '/dashboard');
});
});
});
});
37 changes: 23 additions & 14 deletions app/web/cypress/e2e/modelling-functionality/create-component.cy.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,48 @@
// @ts-check
///<reference path="../global.d.ts"/>

Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VITE_SI_CYPRESS_MULTIPLIER : 1, () => {
const SI_CYPRESS_MULTIPLIER = Cypress.env('VITE_SI_CYPRESS_MULTIPLIER') || import.meta.env.VITE_SI_CYPRESS_MULTIPLIER || 1;
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_API_URL = Cypress.env('VITE_AUTH_API_URL') || import.meta.env.VITE_AUTH_API_URL;
const SI_WORKSPACE_ID = Cypress.env('VITE_SI_WORKSPACE_ID') || import.meta.env.VITE_SI_WORKSPACE_ID;
const UUID = Cypress.env('VITE_UUID') || import.meta.env.VITE_UUID || "local";

Cypress._.times(SI_CYPRESS_MULTIPLIER, () => {
describe('component', () => {
beforeEach(function () {
cy.loginToAuth0(import.meta.env.VITE_AUTH0_USERNAME, import.meta.env.VITE_AUTH0_PASSWORD);
//cy.setupVariables();
cy.loginToAuth0(AUTH0_USERNAME, AUTH0_PASSWORD);
});

it('create', () => {
cy.visit('/')
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.visit(AUTH_API_URL + '/workspaces/' + SI_WORKSPACE_ID + '/go');
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", UUID);

cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', 'Change Set 1');

cy.get('#vorm-input-3').clear().type(import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3').clear().type(UUID);

cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', UUID);

cy.contains('Create change set', { timeout: 30000 }).click();

// Give time to redirect onto the new changeset
cy.url().should('not.include', 'head', { timeout: 10000 });

// Find the AWS Credential
cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('AWS Credential').should('be.visible').as('awsCred')
// Find the AWS Region
cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('Region').as('region');

// Find the canvas to get a location to drag to
cy.get('canvas').first().as('konvaStage');

// drag to the canvas
cy.dragTo('@awsCred', '@konvaStage');
cy.dragTo('@region', '@konvaStage');

cy.wait(5000);

//check to make sure a component has been added to the outliner
cy.get('[class="component-outline-node"]', { timeout: 30000 }).contains('AWS Credential', { timeout: 30000 }).should('be.visible');
cy.get('[class="component-outline-node"]', { timeout: 30000 }).contains('Region', { timeout: 30000 }).should('be.visible');

// Click the button to destroy changeset
cy.get('nav.navbar button.vbutton.--variant-ghost.--size-sm.--tone-action')
Expand All @@ -46,7 +56,6 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
cy.get('button.vbutton.--variant-solid.--size-md.--tone-destructive')
.click();

})
})

});
});
});
});
33 changes: 20 additions & 13 deletions app/web/cypress/e2e/modelling-functionality/delete-component.cy.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
// @ts-check
///<reference path="../global.d.ts"/>

Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VITE_SI_CYPRESS_MULTIPLIER : 1, () => {
const SI_CYPRESS_MULTIPLIER = Cypress.env('VITE_SI_CYPRESS_MULTIPLIER') || import.meta.env.VITE_SI_CYPRESS_MULTIPLIER || 1;
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_API_URL = Cypress.env('VITE_AUTH_API_URL') || import.meta.env.VITE_AUTH_API_URL;
const SI_WORKSPACE_ID = Cypress.env('VITE_SI_WORKSPACE_ID') || import.meta.env.VITE_SI_WORKSPACE_ID;
const UUID = Cypress.env('VITE_UUID') || import.meta.env.VITE_UUID || "local";

Cypress._.times(SI_CYPRESS_MULTIPLIER, () => {
describe('component', () => {
beforeEach(function () {
cy.loginToAuth0(import.meta.env.VITE_AUTH0_USERNAME, import.meta.env.VITE_AUTH0_PASSWORD);
cy.loginToAuth0(AUTH0_USERNAME, AUTH0_PASSWORD);
});

it('delete', () => {
cy.visit('/')
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.visit(AUTH_API_URL + '/workspaces/' + SI_WORKSPACE_ID + '/go');
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", UUID);
cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', 'Change Set 1');

cy.get('#vorm-input-3').clear().type(import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3').clear().type(UUID);

cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', UUID);

cy.contains('Create change set', { timeout: 30000 }).click();

// Give time to redirect onto the new changeset
cy.url().should('not.include', 'head', { timeout: 10000 });

// Find the AWS Credential
cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('AWS Credential').should('be.visible').as('awsCred')
cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('AWS Credential').as('awsCred');

// Find the canvas to get a location to drag to
cy.get('canvas').first().as('konvaStage');
Expand All @@ -31,8 +38,8 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
cy.dragTo('@awsCred', '@konvaStage');

// Check to make sure a component has been added to the outliner
cy.get('[class="component-outline-node"]', { timeout: 30000 }).
contains('AWS Credential', { timeout: 30000 })
cy.get('[class="component-outline-node"]', { timeout: 30000 })
.contains('AWS Credential', { timeout: 30000 })
.should('be.visible')
.rightclick();

Expand All @@ -43,7 +50,7 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
cy.get('button.vbutton.--variant-solid.--size-md.--tone-destructive')
.click();

//check to make sure a component has been added to the outliner
// Check to make sure a component has been added to the outliner
cy.get('[class="component-outline-node"]', { timeout: 30000 }).contains('AWS Credential', { timeout: 30000 }).should('be.visible');

// Click the button to destroy changeset
Expand All @@ -58,6 +65,6 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
cy.get('button.vbutton.--variant-solid.--size-md.--tone-destructive')
.click();

})
})
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,56 +1,107 @@
// @ts-check
///<reference path="../global.d.ts"/>

Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VITE_SI_CYPRESS_MULTIPLIER : 1, () => {
const SI_CYPRESS_MULTIPLIER = Cypress.env('VITE_SI_CYPRESS_MULTIPLIER') || import.meta.env.VITE_SI_CYPRESS_MULTIPLIER || 1;
const AUTH0_USERNAME = Cypress.env('VITE_AUTH0_USERNAME') || import.meta.env.VITE_AUTH0_USERNAME;
const AUTH0_PASSWORD = Cypress.env('VITE_AUTH0_PASSWORD') || import.meta.env.VITE_AUTH0_PASSWORD;
const AUTH_API_URL = Cypress.env('VITE_AUTH_API_URL') || import.meta.env.VITE_AUTH_API_URL;
const SI_WORKSPACE_ID = Cypress.env('VITE_SI_WORKSPACE_ID') || import.meta.env.VITE_SI_WORKSPACE_ID;
const SI_WORKSPACE_URL = Cypress.env('VITE_SI_WORKSPACE_URL') || import.meta.env.VITE_SI_WORKSPACE_URL;
const UUID = Cypress.env('VITE_UUID') || import.meta.env.VITE_UUID || "local";

Cypress._.times(SI_CYPRESS_MULTIPLIER, () => {
describe('component', () => {
beforeEach(function () {
cy.loginToAuth0(import.meta.env.VITE_AUTH0_USERNAME, import.meta.env.VITE_AUTH0_PASSWORD);
cy.loginToAuth0(AUTH0_USERNAME, AUTH0_PASSWORD);
});

it('value_propagation', () => {

console.log(import.meta.env.VITE_UUID);
cy.log(import.meta.env.VITE_UUID);
console.log(UUID);
cy.log(UUID);

// Go to the Synthetic Workspace
cy.visit(import.meta.env.VITE_SI_WORKSPACE_URL + '/w/' + import.meta.env.VITE_SI_WORKSPACE_ID + '/head')
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");

cy.visit(SI_WORKSPACE_URL + '/w/' + SI_WORKSPACE_ID + '/head');
cy.sendPosthogEvent(Cypress.currentTest.titlePath.join("/"), "test_uuid", UUID);
cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', 'Change Set 1');

cy.get('#vorm-input-3').clear().type(import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3').clear().type(UUID);

cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', import.meta.env.VITE_UUID ? import.meta.env.VITE_UUID: "local");
cy.get('#vorm-input-3', { timeout: 30000 }).should('have.value', UUID);

cy.contains('Create change set', { timeout: 30000 }).click();

// Give time to redirect onto the new changeset
cy.url().should('not.include', 'head', { timeout: 10000 });

// Find the AWS Credential
cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('Region').as('awsRegion');

// Find the canvas to get a location to drag to
cy.get('canvas').first().as('konvaStage');

cy.intercept('POST', '/api/diagram/create_component').as('componentA');
let componentIDA, componentIDB, bearertoken;

// drag to the canvas
cy.dragTo('@awsRegion', '@konvaStage');
cy.wait('@componentA', {timeout: 60000}).then(async (interception) => {
componentIDA = interception.response?.body.componentId;
});

cy.wait(5000);

cy.get('div[class="tree-node"]', { timeout: 30000 }).contains('EC2 Instance').as('awsEC2');

cy.intercept('POST', '/api/diagram/create_component').as('componentB');
cy.dragTo('@awsEC2', '@konvaStage', 0, 75);

cy.wait('@componentB', {timeout: 60000}).then(async (interception) => {
bearertoken = interception.request.headers.authorization;
componentIDB = interception.response?.body.componentId;
});

// WE CANNOT FORCE A HOVER STATE INSIDE CANVAS
// SO THE "PARENT" IS NOT SET DESPITE COMPONENT B BEING INSIDE THE FRAME
// HACK
cy.url().then(currentUrl => {
const parts = currentUrl.split('/');
parts.pop(); // c
const changeset = parts.pop();
cy.request({
method: 'POST',
url: '/api/diagram/connect_component_to_frame',
body: JSON.stringify({
childId: componentIDB,
parentId: componentIDA,
visibility_change_set_pk: changeset,
workspaceId: SI_WORKSPACE_ID
}),
headers: { Authorization: bearertoken, "Content-Type": 'application/json' }
});
});

cy.wait(2000);

cy.url().then(currentUrl => {
// Construct a new URL with desired query parameters for selecting
// the attribute panel for a known component
let newUrl = new URL(currentUrl);
newUrl.searchParams.set('s', import.meta.env.VITE_SI_PROPAGATION_COMPONENT_A);
newUrl.searchParams.set('s', 'c_'+componentIDA);
newUrl.searchParams.set('t', 'attributes');

// Visit the new URL
console.log(newUrl.href);
cy.visit(newUrl.href);
});

// Give the page a few seconds to load
cy.wait(2000);

// Generate a random number between 1 and 100 to insert into the
// attribute value for Integer
const randomNumber = Math.floor(Math.random() * 100) + 1;

cy.intercept('POST', '/api/component/update_property_editor_value').as('updatePropertyEditorValue');

// Find the attribute for the Integer Input
cy.get('.attributes-panel-item__input-wrap input[type="number"]')
.clear()
.type(randomNumber.toString() + '{enter}') // type the new value
cy.get('.attributes-panel-item__input-wrap select:first')
.select('us-east-1');

// Intercept the API call and alias it
cy.wait('@updatePropertyEditorValue', { timeout: 60000 }).its('response.statusCode').should('eq', 200);
Expand All @@ -59,17 +110,17 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
// Construct a new URL with desired query parameters for selecting
// the attribute panel for a known connected component
let newUrl = new URL(currentUrl);
newUrl.searchParams.set('s', import.meta.env.VITE_SI_PROPAGATION_COMPONENT_B);
newUrl.searchParams.set('s', 'c_'+componentIDB);
newUrl.searchParams.set('t', 'attributes');
cy.visit(newUrl.href);
});

// Wait for the values to propagate
cy.wait(60000);
cy.wait(1000);

// Validate that the value has propogated through the system
cy.get('.attributes-panel-item__input-wrap input[type="number"]', { timeout: 30000 })
.should('have.value', randomNumber.toString(), { timeout: 30000 });
// Validate that the value has propagated through the system
cy.get('.attributes-panel-item__input-wrap input.region')
.should('have.value', 'us-east-1');

// Click the button to destroy changeset
cy.get('nav.navbar button.vbutton.--variant-ghost.--size-sm.--tone-action')
Expand All @@ -83,6 +134,6 @@ Cypress._.times(import.meta.env.VITE_SI_CYPRESS_MULTIPLIER ? import.meta.env.VIT
cy.get('button.vbutton.--variant-solid.--size-md.--tone-destructive')
.click();

})
})
});
});
});
});
Loading

0 comments on commit ab23fd7

Please sign in to comment.