mirror of
https://github.com/penpot/penpot.git
synced 2026-06-28 02:02:06 +00:00
219 lines
7.1 KiB
JavaScript
219 lines
7.1 KiB
JavaScript
// Workspace Open Performance Test (Read-heavy)
|
|
//
|
|
// Simulates a user opening a file in the workspace editor.
|
|
// This is the most common read-heavy operation — loading a file and its
|
|
// dependencies (libraries, thumbnails).
|
|
//
|
|
// Flow:
|
|
// 1. Register (demo profile)
|
|
// 2. Login
|
|
// 3. Get teams → default team
|
|
// 4. Create project → create file (setup data)
|
|
// 5. Update file with a shape (so the file has data)
|
|
// 6. Loop: get-file → get-file-libraries → get-file-object-thumbnails
|
|
// → get-file-data-for-thumbnail
|
|
//
|
|
// Usage:
|
|
// k6 run scripts/workspace-open.js
|
|
// k6 run --env PENPOT_BASE_URL=http://localhost:6060 scripts/workspace-open.js
|
|
// k6 run --vus 10 --iterations 5 scripts/workspace-open.js
|
|
|
|
import { check, sleep, fail } from "k6";
|
|
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
|
|
import { createClient } from "../lib/penpot-client.js";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Configuration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const BASE_URL = __ENV.PENPOT_BASE_URL || "http://localhost:6060";
|
|
const OPEN_ITERATIONS = parseInt(__ENV.PENPOT_OPEN_ITERATIONS || "5");
|
|
|
|
export const options = {
|
|
scenarios: {
|
|
workspace_open: {
|
|
executor: "per-vu-iterations",
|
|
vus: 1,
|
|
iterations: 1,
|
|
maxDuration: "2m",
|
|
},
|
|
},
|
|
thresholds: {
|
|
http_req_duration: ["p(95)<5000"],
|
|
http_req_failed: ["rate<0.01"],
|
|
"http_req_duration{rpc_command:get-file}": ["p(95)<500"],
|
|
"http_req_duration{rpc_command:get-file-libraries}": ["p(95)<500"],
|
|
"http_req_duration{rpc_command:get-file-object-thumbnails}": ["p(95)<500"],
|
|
"http_req_duration{rpc_command:get-file-data-for-thumbnail}": ["p(95)<500"],
|
|
},
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function assertOk(res, label) {
|
|
const ok = check(res, {
|
|
[`${label} — status is 2xx`]: (r) => r.status >= 200 && r.status < 300,
|
|
});
|
|
if (!ok) {
|
|
let bodyStr = "";
|
|
try {
|
|
if (res.raw && res.raw.body) {
|
|
bodyStr = typeof res.raw.body === "string"
|
|
? res.raw.body.substring(0, 500)
|
|
: JSON.stringify(res.raw.body).substring(0, 500);
|
|
} else if (res.body) {
|
|
bodyStr = JSON.stringify(res.body).substring(0, 500);
|
|
}
|
|
} catch (e) {
|
|
bodyStr = "(could not read body)";
|
|
}
|
|
console.error(`FAIL: ${label} — status=${res.status} body=${bodyStr}`);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Setup — create a file with data that we'll open repeatedly
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function setup() {
|
|
console.log(`Penpot Workspace Open Test`);
|
|
console.log(` Base URL: ${BASE_URL}`);
|
|
console.log(` Open iterations: ${OPEN_ITERATIONS}`);
|
|
console.log(``);
|
|
|
|
const client = createClient(BASE_URL);
|
|
|
|
// Small random delay to avoid demo profile creation race with parallel scripts
|
|
sleep(Math.random() * 3);
|
|
|
|
// Create demo profile
|
|
const demoRes = client.rpc("POST", "create-demo-profile", {});
|
|
if (demoRes.status !== 200) {
|
|
fail("Failed to create demo profile — is demo-users flag enabled?");
|
|
}
|
|
const demoBody = demoRes.json();
|
|
console.log(` Created demo profile: ${demoBody.email}`);
|
|
|
|
// Login
|
|
const loginRes = client.login(demoBody.email, demoBody.password);
|
|
if (loginRes.status !== 200) fail("Login failed");
|
|
|
|
// Get default team
|
|
const teamsRes = client.getTeams();
|
|
if (teamsRes.status !== 200) fail("get-teams failed");
|
|
const teamId = teamsRes.body[0].id;
|
|
|
|
// Create project
|
|
const projRes = client.createProject(teamId, "WS Open Project");
|
|
if (projRes.status !== 200) fail("create-project failed");
|
|
const projectId = projRes.body.id;
|
|
|
|
// Create file
|
|
const fileRes = client.createFile(projectId, "WS Open File");
|
|
if (fileRes.status !== 200) fail("create-file failed");
|
|
const fileId = fileRes.body.id;
|
|
|
|
// Get file to read page-id, revn, vern
|
|
const getFileRes = client.getFile(fileId);
|
|
if (getFileRes.status !== 200) fail("get-file failed");
|
|
const fileData = getFileRes.body;
|
|
const pageId = fileData.data.pages[0];
|
|
|
|
// Add a shape so the file has meaningful data
|
|
const shapeId = uuidv4();
|
|
const x = 50;
|
|
const y = 50;
|
|
const w = 300;
|
|
const h = 200;
|
|
const changes = [{
|
|
type: "add-obj",
|
|
pageId: pageId,
|
|
id: shapeId,
|
|
frameId: pageId,
|
|
parentId: pageId,
|
|
obj: {
|
|
id: shapeId, type: "rect", name: "Background",
|
|
x, y, width: w, height: h,
|
|
fillColor: "#cccccc", fillOpacity: 1,
|
|
rotation: 0, hidden: false, locked: false,
|
|
selrect: { x, y, width: w, height: h, x1: x, y1: y, x2: x + w, y2: y + h },
|
|
points: [
|
|
{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h },
|
|
],
|
|
transform: { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
transformInverse: { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
parentId: pageId, frameId: pageId,
|
|
},
|
|
}];
|
|
const updateRes = client.updateFile(fileId, fileData.revn, fileData.vern, client.sessionId, changes);
|
|
if (updateRes.status !== 200) fail("update-file (seed shape) failed");
|
|
|
|
console.log(` File ready: ${fileId} (page: ${pageId})`);
|
|
|
|
return {
|
|
baseUrl: BASE_URL,
|
|
fileId,
|
|
projectId,
|
|
teamId,
|
|
profileId: loginRes.body.id,
|
|
email: demoBody.email,
|
|
password: demoBody.password,
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main VU Function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export default function (data) {
|
|
const client = createClient(data.baseUrl);
|
|
|
|
// Small random delay to spread out concurrent VU logins
|
|
sleep(Math.random() * 2);
|
|
|
|
// Login with the same profile that owns the file
|
|
const loginRes = client.login(data.email, data.password);
|
|
if (!assertOk(loginRes, "login")) fail("login failed");
|
|
|
|
sleep(0.5);
|
|
|
|
for (let i = 0; i < OPEN_ITERATIONS; i++) {
|
|
// 1. Get file
|
|
const getFileRes = client.getFile(data.fileId);
|
|
if (!assertOk(getFileRes, "get-file")) fail("get-file failed");
|
|
|
|
sleep(0.3);
|
|
|
|
// 2. Get file libraries
|
|
const libsRes = client.getFileLibraries(data.fileId);
|
|
if (!assertOk(libsRes, "get-file-libraries")) fail("get-file-libraries failed");
|
|
|
|
sleep(0.2);
|
|
|
|
// 3. Get object thumbnails
|
|
const thumbsRes = client.getFileObjectThumbnails(data.fileId);
|
|
if (!assertOk(thumbsRes, "get-file-object-thumbnails")) fail("get-file-object-thumbnails failed");
|
|
|
|
sleep(0.2);
|
|
|
|
// 4. Get file data for thumbnail
|
|
const thumbDataRes = client.getFileDataForThumbnail(data.fileId);
|
|
if (!assertOk(thumbDataRes, "get-file-data-for-thumbnail")) fail("get-file-data-for-thumbnail failed");
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
console.log(`VU ${__VU}: Completed ${OPEN_ITERATIONS} workspace open iterations`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Teardown
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function teardown(data) {
|
|
console.log("Workspace open test complete.");
|
|
}
|