Skip to content

Commit 63b5fee

Browse files
committed
e2e-tests: add cypress tests for scale cluster
This commit adds files to enable the use of Cypress to smoke test DB Console on a cluster to see if it meets basic performance requirements. This can be used on a scale cluster to make sure we can render all the standard pages in a reasonable amount of time. The test will print out timing information for key elements in the logs. Additionally, screenshots are taken immediately after the timing snapshot to enable easy analysis of what's on the page at time of timer capture. You can then confirm that the page is "useful". Resolves: #51018 Release note: None
1 parent b629ba8 commit 63b5fee

File tree

6 files changed

+247
-0
lines changed

6 files changed

+247
-0
lines changed

pkg/ui/workspaces/e2e-tests/cypress.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default defineConfig({
1717
e2e: {
1818
baseUrl: "http://localhost:8080",
1919
video: true,
20+
numTestsKeptInMemory: 0,
2021
setupNodeEvents(on, config) {
2122
config.env.username = "cypress";
2223
config.env.password = "tests";
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
import { defineConfig } from "cypress";
7+
8+
const DOCKER_OVERRIDES: Partial<Cypress.UserConfigOptions["e2e"]> = {
9+
baseUrl: "https://crdbhost:8080",
10+
reporter: "teamcity",
11+
downloadsFolder: "/artifacts/cypress/scale/downloads",
12+
screenshotsFolder: "/artifacts/cypress/scale/screenshots",
13+
videosFolder: "/artifacts/cypress/scale/videos",
14+
};
15+
16+
export default defineConfig({
17+
e2e: {
18+
baseUrl: "http://localhost:8080",
19+
video: true,
20+
specPattern: "cypress/scale/**/*.{js,jsx,ts,tsx}",
21+
numTestsKeptInMemory: 0,
22+
viewportWidth: 1920,
23+
viewportHeight: 1080,
24+
setupNodeEvents(on, config) {
25+
on("task", {
26+
logTiming: (data) => {
27+
console.log(
28+
`📊 Measured Timing: ${data.renderName} - ${data.duration.toFixed(
29+
2,
30+
)}ms`,
31+
);
32+
return null;
33+
},
34+
});
35+
return config;
36+
},
37+
// Override some settings when running in Docker
38+
...(process.env.IS_DOCKER ? DOCKER_OVERRIDES : {}),
39+
},
40+
});
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
describe("Scale Testing - DB Console", () => {
7+
beforeEach(() => {
8+
cy.login("roachprod", "cockroachdb");
9+
});
10+
11+
it("loads the overview page within acceptable time", () => {
12+
cy.measureRender(
13+
"Overview Page",
14+
() => cy.visit("#/"),
15+
() =>
16+
cy
17+
.get(".node-liveness.cluster-summary__metric.live-nodes", {
18+
timeout: 30000,
19+
})
20+
.invoke("text")
21+
.then((text) => {
22+
const value = parseInt(text.trim(), 10);
23+
expect(value).to.be.greaterThan(0);
24+
}),
25+
);
26+
27+
cy.get(".cluster-overview").should("be.visible");
28+
});
29+
it("loads the Metrics page and displays the correct heading and key elements", () => {
30+
cy.measureRender(
31+
"Metrics Page Graph",
32+
() => cy.visit("#/metrics/hardware/cluster"),
33+
() =>
34+
cy.get("canvas", { timeout: 30000 }).then(($canvas) => {
35+
const canvas = $canvas[0];
36+
const ctx = canvas.getContext("2d");
37+
const { width, height } = canvas;
38+
const imageData = ctx.getImageData(0, 0, width, height).data;
39+
40+
// Check if any pixel is not fully transparent (alpha > 0)
41+
const hasPixels = Array.from({ length: width * height }).some(
42+
(_, i) => {
43+
return imageData[i * 4 + 3] !== 0; // alpha channel
44+
},
45+
);
46+
47+
expect(hasPixels).to.be.true;
48+
}),
49+
);
50+
51+
cy.get("h3").contains("Metrics").should("be.visible");
52+
cy.contains("Dashboard:").should("be.visible");
53+
cy.get("button")
54+
.contains(/Past Hour|1h/)
55+
.should("be.visible");
56+
});
57+
58+
it("loads the Databases page, refreshes, and displays the correct heading and key elements", () => {
59+
cy.measureRender(
60+
"Databases Page",
61+
() => cy.visit("#/databases"),
62+
() =>
63+
cy
64+
.get("[data-row-key='1']", { timeout: 30000 })
65+
.contains("system")
66+
.should("exist"),
67+
);
68+
69+
cy.get("h3").contains("Databases").should("be.visible");
70+
});
71+
72+
it("loads the SQL Activity page and displays the correct heading and key elements", () => {
73+
cy.measureRender(
74+
"SQL Activity Page Table",
75+
() => cy.visit("#/sql-activity"),
76+
() => cy.get("table tr td", { timeout: 30000 }).should("exist"),
77+
);
78+
79+
cy.get("h3").contains("SQL Activity").should("be.visible");
80+
cy.get('[role="tablist"]').should("be.visible");
81+
cy.get('[role="tab"]').contains("Statements").should("be.visible");
82+
cy.get('[role="tab"]').contains("Transactions").should("be.visible");
83+
cy.get('[role="tab"]').contains("Sessions").should("be.visible");
84+
cy.get("button")
85+
.contains(/Past Hour|1h/)
86+
.should("be.visible");
87+
});
88+
89+
it("loads the Jobs page and displays the correct heading and key elements", () => {
90+
cy.measureRender(
91+
"Jobs Page Table",
92+
() => cy.visit("#/jobs"),
93+
() => cy.get("table tr td", { timeout: 30000 }).should("exist"),
94+
);
95+
96+
cy.get("h3").contains("Jobs").should("be.visible");
97+
98+
cy.get("button")
99+
.contains(/Status:/)
100+
.should("be.visible");
101+
cy.get("button").contains(/Type:/).should("be.visible");
102+
cy.get("button").contains(/Show:/).should("be.visible");
103+
});
104+
105+
it("loads the Network page and displays the correct heading and key elements", () => {
106+
cy.measureRender(
107+
"Network Page Table",
108+
() => cy.visit("#/reports/network/region"),
109+
() => cy.get("table", { timeout: 30000 }).should("exist"),
110+
);
111+
112+
cy.get("h3").contains("Network").should("be.visible");
113+
cy.contains("Sort By:").should("be.visible");
114+
});
115+
116+
it("loads the Insights page and displays the correct heading and key elements", () => {
117+
cy.measureRender(
118+
"Insights Page Table",
119+
() => cy.visit("#/insights"),
120+
() => cy.get("table tr td", { timeout: 30000 }).should("exist"),
121+
);
122+
123+
cy.get("h3").contains("Insights").should("be.visible");
124+
cy.get('[role="tablist"]').should("be.visible");
125+
cy.get('[role="tab"]').contains("Workload Insights").should("be.visible");
126+
cy.get('[role="tab"]').contains("Schema Insights").should("be.visible");
127+
cy.get("button")
128+
.contains(/Past Hour|1h/)
129+
.should("be.visible");
130+
});
131+
132+
it("loads the Hot Ranges page and displays the correct heading and key elements", () => {
133+
cy.measureRender(
134+
"Hot Ranges Table",
135+
() => cy.visit("#/hotranges"),
136+
() => cy.get("table tr td", { timeout: 30000 }).should("exist"),
137+
);
138+
});
139+
140+
it("loads the Schedules page and displays the correct heading and key elements", () => {
141+
cy.measureRender(
142+
"Schedules Page",
143+
() => cy.visit("#/schedules"),
144+
() => cy.get("table tr td", { timeout: 30000 }).should("exist"),
145+
);
146+
147+
cy.get("h3").contains("Schedules").should("be.visible");
148+
cy.get("button")
149+
.contains(/Status:/)
150+
.should("be.visible");
151+
cy.get("button").contains(/Show:/).should("be.visible");
152+
});
153+
});

pkg/ui/workspaces/e2e-tests/cypress/support/commands.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,49 @@ Cypress.Commands.add("getUserWithExactPrivileges", (privs: SQLPrivilege[]) => {
2323
);
2424
});
2525
});
26+
27+
// measureRender can capture render time for a given action. Currently this will
28+
// only work in scale tests because the `logTiming` task is only available in
29+
// scale tests.
30+
Cypress.Commands.add(
31+
"measureRender",
32+
{ prevSubject: "optional" },
33+
(subject, renderName, actionFn, assertionChain) => {
34+
// If subject is provided, it means the command is chained (e.g., cy.get('selector').measureRender(...))
35+
const chainable = subject ? cy.wrap(subject) : cy;
36+
37+
let startTime;
38+
39+
chainable
40+
.then(() => {
41+
startTime = performance.now();
42+
})
43+
.then(() => {
44+
// Execute the action function provided by the user
45+
// The actionFn should return a chainable Cypress command
46+
return actionFn();
47+
})
48+
.then((actionResult) => {
49+
// Assert on the element/state that indicates render completion
50+
// The assertionChain should be a function that takes the result
51+
// of the action and returns a chainable assertion
52+
const assertionChainable = assertionChain(actionResult);
53+
54+
// Ensure the assertion chain is properly waited on
55+
return assertionChainable.then(() => {
56+
const endTime = performance.now();
57+
const duration = endTime - startTime;
58+
const testTitle = Cypress.currentTest.title;
59+
60+
cy.task("logTiming", {
61+
testTitle,
62+
renderName,
63+
duration,
64+
});
65+
cy.screenshot(`timing-${renderName}-${Date.now()}`);
66+
67+
return assertionChainable;
68+
});
69+
});
70+
},
71+
);

pkg/ui/workspaces/e2e-tests/cypress/support/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ declare global {
3434
*/
3535
login(username: string, password: string): Chainable<void>;
3636
getUserWithExactPrivileges(privs: SQLPrivilege[]): Chainable<User>;
37+
measureRender(
38+
renderName: string,
39+
actionFn: () => Chainable<any>,
40+
assertionChain: (result: any) => Chainable<any>,
41+
): Chainable<any>;
3742
}
3843
}
3944
}

pkg/ui/workspaces/e2e-tests/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"test:debug": "./build/start-crdb-then.sh pnpm cy:debug",
1010
"cy:run": "cypress run --e2e",
1111
"cy:debug": "cypress open --e2e --browser=chrome",
12+
"cy:scale:debug": "cypress open --config-file cypress.scale.config.ts --browser=chrome",
13+
"cy:scale": "cypress run --config-file cypress.scale.config.ts --browser=chrome",
1214
"lint": "eslint './**/*.ts'",
1315
"lint:fix": "eslint --fix './**/*.ts'"
1416
},

0 commit comments

Comments
 (0)