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

e2e test that creates GKE cluster with default settings #12934

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export default defineConfig({
azureClientId: process.env.AZURE_CLIENT_ID,
azureClientSecret: process.env.AZURE_CLIENT_SECRET,
customNodeIp: process.env.CUSTOM_NODE_IP,
customNodeKey: process.env.CUSTOM_NODE_KEY
customNodeKey: process.env.CUSTOM_NODE_KEY,
gkeServiceAccount: process.env.GKE_SERVICE_ACCOUNT,
},
e2e: {
fixturesFolder: 'cypress/e2e/blueprints',
Expand Down
12 changes: 12 additions & 0 deletions cypress/e2e/po/components/button-file-selector.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';

export default class ButtonFileSelectorPo extends ComponentPo {
/**
* Returns a file-selector button
* @param id
* @returns
*/
static readFromFileButton(): Cypress.Chainable {
return cy.getId('file-selector__uploader-button');
}
}
18 changes: 18 additions & 0 deletions cypress/e2e/po/edit/cloud-credentials-gke.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po';
import ButtonFileSelectorPo from '@/cypress/e2e/po/components/button-file-selector.po';
import BaseCloudCredentialsPo from '@/cypress/e2e/po/edit/base-cloud-credentials.po';
import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';

export default class GKECloudCredentialsCreateEditPo extends BaseCloudCredentialsPo {
serviceAccount(): LabeledInputPo {
return LabeledInputPo.byLabel(this.self(), 'Service Account');
}

readFromFile(): Cypress.Chainable {
return ButtonFileSelectorPo.readFromFileButton().click();
}

authenticateButton() {
return new AsyncButtonPo('[data-testid="action-button-async-button"]', this.self());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import PagePo from '@/cypress/e2e/po/pages/page.po';
import ClusterManagerCreatePagePo from '@/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create.po';
import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po';
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';
import GKECloudCredentialsCreateEditPo from '@/cypress/e2e/po/edit/cloud-credentials-gke.po';
import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';

/**
* Create page for a GKE cluster
*/
export default class ClusterManagerCreateGKEPagePo extends ClusterManagerCreatePagePo {
static url(clusterId: string) {
return `${ ClusterManagerCreatePagePo.url(clusterId) }/create?type=googlegke`;
}

static goTo(clusterId: string): Cypress.Chainable<Cypress.AUTWindow> {
return PagePo.goTo(ClusterManagerCreateGKEPagePo.url(clusterId));
}

goToGKEClusterCreation(clusterId: string): Cypress.Chainable<Cypress.AUTWindow> {
return PagePo.goTo(`${ ClusterManagerCreatePagePo.url(clusterId) }?type=googlegke`);
}

cloudCredentialsForm(): GKECloudCredentialsCreateEditPo {
return new GKECloudCredentialsCreateEditPo();
}

authProjectId(): LabeledInputPo {
return LabeledInputPo.byLabel(this.self(), 'Google Project ID');
}

saveCreateGkeCluster(): AsyncButtonPo {
return new AsyncButtonPo('[data-testid="form-save"]', this.self());
}

static getGkeVersionSelect() {
return new LabeledSelectPo('[data-testid="gke-version-select"]');
}

static getGkeZoneSelect() {
return new LabeledSelectPo('[data-testid="gke-zone-select"]');
}

getClusterName() {
return new LabeledInputPo('[data-testid="gke-cluster-name"]');
}

getClusterDescription() {
return new LabeledInputPo('[data-testid="gke-cluster-description"]');
}
}
139 changes: 139 additions & 0 deletions cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import HomePagePo from '@/cypress/e2e/po/pages/home.po';
import ClusterManagerListPagePo from '@/cypress/e2e/po/pages/cluster-manager/cluster-manager-list.po';
import LoadingPo from '@/cypress/e2e/po/components/loading.po';
import ClusterManagerCreateGKEPagePo from '@/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-gke.po';
import { DEFAULT_GCP_ZONE } from '@/pkg/gke/util/gcp';

/******
* Running this test will delete all GKE cloud credentials from the target cluster
******/

// will only run this in jenkins pipeline where cloud credentials are stored
describe('Deploy GKE cluster with default settings', { tags: ['@manager', '@adminUser', '@jenkins'] }, () => {
const clusterList = new ClusterManagerListPagePo();
const loadingPo = new LoadingPo('.loading-indicator');

let cloudcredentialId = '';
const gkeDefaultZone = 'us-central1-c';
let gkeVersion = '';
let clusterId = '';
let clusterDescription = '';
const gkeProjectId = JSON.parse(Cypress.env('gkeServiceAccount')).project_id;
Copy link
Member

Choose a reason for hiding this comment

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

I believe this is failing CI due to this line. We would need this new variable, GKE_SERVICE_ACCOUNT, present in the environment for it to work correctly. I'm not personally sure how to do it, but maybe @richard-cox or @izaac can instruct us?

Copy link
Member

Choose a reason for hiding this comment

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

error ref:

 1) An uncaught error was detected outside of a test:
     SyntaxError: The following error originated from your test code, not from Cypress.

  > "undefined" is not valid JSON

When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.

Cypress could not associate this error to any specific test.

We dynamically generated a new test to display this failure.
      at JSON.parse (<anonymous>)
      at Suite.eval (webpack:///./cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts:23:0)
      at describeGrep (webpack:///./node_modules/@cypress/grep/src/support.js:183:0)
      at ./cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts (webpack:///./cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts:15:0)
      at __webpack_require__ (webpack:///webpack/bootstrap:19:0)
      at 0 (https://127.0.0.1/__cypress/tests?p=cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts:8117:18)
      at __webpack_require__ (webpack:///webpack/bootstrap:19:0)
      at eval (webpack:///webpack/bootstrap:83:0)
      at eval (https://127.0.0.1/__cypress/tests?p=cypress/e2e/tests/pages/manager/gke-cluster-provisioning.spec.ts:87:10)
      at eval (<anonymous>)

Copy link
Contributor

@izaac izaac Jan 28, 2025

Choose a reason for hiding this comment

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

I wasn't expecting this to run in Github Actions with the @jenkins tag. Only on Jenkins.

Edit: I am not sure how the grepTags change the behavior of Cypress' describe and it blocks internally but I guess the code gets interpreted before the tag logic is applied.

I see other filtered specs still selected but then the greptags logic blocks the test from execution.

Catching the undefined might shed some light to my theory.

Copy link
Contributor

@izaac izaac Jan 28, 2025

Choose a reason for hiding this comment

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

In the meantime we should catch the undefined value before attempting to parse the variable.

Copy link
Contributor

Choose a reason for hiding this comment

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

it seems like it's only an issue with @standardUser tag. we can remove that tag to get unblocked and create a separate issue to investigate this

Copy link
Contributor

@yonasberhe23 yonasberhe23 Jan 29, 2025

Choose a reason for hiding this comment

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

scratch that idea, it didn't work (test is still picked up when @standardUser tag removed). We should handle undefined or null values using try catch as Izaac suggested. Sent Isabela some pointers

Copy link
Contributor

Choose a reason for hiding this comment

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

I've created an issue to investigate the problem we're seeing with the grep tags rancher/qa-tasks#1663


before(() => {
cy.login();
HomePagePo.goTo();

// clean up GKE cloud credentials
cy.getRancherResource('v3', 'cloudcredentials', null, null).then((resp: Cypress.Response<any>) => {
const body = resp.body;

if (body.pagination['total'] > 0) {
body.data.forEach((item: any) => {
if (item.googlecredentialConfig) {
const id = item.id;

cy.deleteRancherResource('v3', 'cloudcredentials', id);
IsaSih marked this conversation as resolved.
Show resolved Hide resolved
} else {
cy.log('There are no existing GKE cloud credentials to delete');
}
});
}
});
});

beforeEach(() => {
cy.createE2EResourceName('gkecluster').as('gkeClusterName');
cy.createE2EResourceName('gkecloudcredential').as('gkeCloudCredentialName');
});

it('Successfully create GKE cluster with default settings', function() {
const createGKEClusterPage = new ClusterManagerCreateGKEPagePo();
const cloudCredForm = createGKEClusterPage.cloudCredentialsForm();

// Select GKE and create GKE cluster page
ClusterManagerListPagePo.navTo();
clusterList.waitForPage();
clusterList.createCluster();
createGKEClusterPage.selectKubeProvider(2);
loadingPo.checkNotExists();
createGKEClusterPage.rke2PageTitle().should('include', 'Create Google GKE');
createGKEClusterPage.waitForPage('type=googlegke&rkeType=rke2');

// create GKE cloud credential
cloudCredForm.saveButton().expectToBeDisabled();
cloudCredForm.nameNsDescription().name().set(this.gkeCloudCredentialName);
cloudCredForm.serviceAccount().set(Cypress.env('gkeServiceAccount'));
cloudCredForm.saveButton().expectToBeEnabled();
cy.intercept('GET', '/v1/management.cattle.io.users?exclude=metadata.managedFields').as('pageLoad');
cloudCredForm.saveCreateForm().cruResource().saveAndWaitForRequests('POST', '/v3/cloudcredentials').then((req) => {
expect(req.response?.statusCode).to.equal(201);
cloudcredentialId = req.response?.body.id.replace(':', '%3A');
IsaSih marked this conversation as resolved.
Show resolved Hide resolved

// Authenticate GKE credential by providing the Project ID
createGKEClusterPage.waitForPage('type=googlegke&rkeType=rke2');
createGKEClusterPage.authProjectId().set( gkeProjectId );
cy.intercept('POST', `/meta/gkeVersions?cloudCredentialId=${ cloudcredentialId }&projectId=${ gkeProjectId }&zone=${ gkeDefaultZone }`).as('getGKEVersions');
cloudCredForm.authenticateButton().click();
cy.wait('@pageLoad').its('response.statusCode').should('eq', 200);
loadingPo.checkNotExists();

// Verify that gke-zone-select dropdown is set to the default zone
createGKEClusterPage.waitForPage('type=googlegke&rkeType=rke2');
ClusterManagerCreateGKEPagePo.getGkeZoneSelect().checkOptionSelected(DEFAULT_GCP_ZONE);

// Get latest GKE kubernetes version and verify that gke-version-select dropdown is set to the default version as defined by versionOptions(); in Config.vue
cy.wait('@getGKEVersions').then(({ response }) => {
expect(response.statusCode).to.eq(200);
gkeVersion = response.body.validMasterVersions[0];
cy.wrap(gkeVersion).as('gkeVersion');
ClusterManagerCreateGKEPagePo.getGkeVersionSelect().checkOptionSelected(gkeVersion);
});

// Set the cluster name and description in the Create GKE Page
createGKEClusterPage.getClusterName().set(this.gkeClusterName);
clusterDescription = `${ this.gkeClusterName }-description`;
createGKEClusterPage.getClusterDescription().set(clusterDescription);
});

// Create GKE Cluster and verify that the properties posted to the server match the expected settings
cy.intercept('POST', 'v3/clusters').as('createGKECluster');

createGKEClusterPage.saveCreateGkeCluster().click();
cy.wait('@createGKECluster').then(({ response }) => {
expect(response?.statusCode).to.eq(201);
expect(response?.body).to.have.property('baseType', 'cluster');
expect(response?.body.gkeConfig).to.have.property('clusterName', this.gkeClusterName);
expect(response?.body).to.have.property('description', clusterDescription);
expect(response?.body.gkeConfig).to.have.property('kubernetesVersion').contains(gkeVersion);
clusterId = response?.body.id;
});

// Verify that the GKE created cluster is listed in the clusters list and has the Provisioning status
clusterList.waitForPage();
clusterList.list().state(this.gkeClusterName).should('contain.text', 'Provisioning');
});

after('clean up', () => {
// delete cluster
cy.deleteRancherResource('v1', 'provisioning.cattle.io.clusters', `fleet-default/${ clusterId }`, false);

// clean up GKE cloud credentials
cy.getRancherResource('v3', 'cloudcredentials', null, null).then((resp: Cypress.Response<any>) => {
const body = resp.body;

if (body.pagination['total'] > 0) {
body.data.forEach((item: any) => {
if (item.googlecredentialConfig) {
const id = item.id;

cy.deleteRancherResource('v3', 'cloudcredentials', id);
} else {
cy.log('There are no existing GKE cloud credentials to delete');
}
});
}
});
}
);
});
8 changes: 6 additions & 2 deletions pkg/gke/components/CruGKE.vue
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,9 @@ export default defineComponent({
<div
class="row mb-10"
>
<div class="col span-6">
<div
class="col span-6"
>
<LabeledInput
:value="normanCluster.name"
:mode="mode"
Expand All @@ -708,7 +710,9 @@ export default defineComponent({
@update:value="setClusterName"
/>
</div>
<div class="col span-6">
<div
class="col span-6"
>
<LabeledInput
:value="normanCluster.description"
:mode="mode"
Expand Down
Loading