Skip to content

Instantly share code, notes, and snippets.

@somoza
Last active August 23, 2024 11:19
Show Gist options
  • Save somoza/6b5c336c46c5aca3aa92309aa7d9be2a to your computer and use it in GitHub Desktop.
Save somoza/6b5c336c46c5aca3aa92309aa7d9be2a to your computer and use it in GitHub Desktop.
K6 script to test Phoenix Liveview entire lifecycle, included socket connection.
import http from "k6/http";
import { sleep, check, fail } from "k6";
import ws from "k6/ws";
export default function () {
const protocol = "https"; // You might want to avoid Cloudflare's DDoS protection or bot mitigation
const host = "domain.com";
const origin = `${protocol}://${host}`;
const path = "/path/to/test";
const url = `${origin}${path}`;
const wsProtocol = "ws";
// Prevent errors redirection / loops
const options = {
redirects: 0,
};
const httpResponse = http.get(url, options);
const { name: cookie_name, value: cookie_value } =
getLiveViewCookie(httpResponse);
const WSResponse = doLiveViewUpgrade(
host,
origin,
wsProtocol,
cookie_name,
cookie_value,
httpResponse,
url
);
checkStatus(WSResponse, 101);
check(httpResponse, {
"status 200": (r) => r.status === 200,
});
sleep(1);
}
function getLiveViewCookie(res) {
let cookies = res.cookies;
if (!cookies) {
console.error("Cookie doesn't found");
return false;
} else {
const [cookieKey] = Object.keys(cookies);
const liveViewCookie = cookies[cookieKey];
return liveViewCookie[0];
}
}
function doLiveViewUpgrade(
host,
testHost,
wsProto,
cookie_name,
cookie_value,
response,
url,
opts = {}
) {
const debug = opts.debug || false;
// The response html contains the LV websocket connection details
const props = grabLVProps(response);
const wsCsrfToken = props.wsCsrfToken;
const phxSession = props.phxSession;
const phxStatic = props.phxStatic;
const topic = `lv:${props.phxId}`;
const ws_url = `${wsProto}://${host}/live/websocket?vsn=2.0.0&_csrf_token=${wsCsrfToken}`;
if (debug) console.log(`connecting ${ws_url}`);
// LV handshake message
const joinMsg = JSON.stringify(
encodeMsg(null, 0, topic, "phx_join", {
url: url,
params: {
_csrf_token: wsCsrfToken,
_mounts: 0,
},
session: phxSession,
static: phxStatic,
})
);
console.log("Cookie", `${cookie_name}=${cookie_value}`);
var response = ws.connect(
ws_url,
{
headers: {
Cookie: `${cookie_name}=${cookie_value}`,
Origin: testHost,
},
},
function (socket) {
socket.on("open", () => {
socket.send(joinMsg);
if (debug) console.log(`websocket open: phx_join topic: ${topic}`);
}),
socket.on("message", (message) => {
checkMessage(message, `"status":"ok"`);
socket.close();
});
socket.on("error", handleWsError);
socket.on("close", () => {
// should we issue a phx_leave here?
if (debug) console.log("websocket disconnected");
});
socket.setTimeout(() => {
console.log("2 seconds passed, closing the socket");
socket.close();
fail("websocket closed");
}, 2000);
}
);
return response;
}
function encodeMsg(id, seq, topic, event, msg) {
return [`${id}`, `${seq}`, topic, event, msg];
}
function handleWsError(e) {
if (e.error() != "websocket: close sent") {
let msg = `An unexpected error occurred: ${e.error()}`;
if (debug) console.log(msg);
fail(msg);
}
}
function grabLVProps(response) {
let elem = response.html().find("meta[name='csrf-token']");
let wsCsrfToken = elem.attr("content");
if (!check(wsCsrfToken, { "found WS token ": (token) => !!token })) {
fail("websocket csrf token not found");
}
elem = response.html().find("div[data-phx-main]");
let phxSession = elem.data("phx-session");
let phxStatic = elem.data("phx-static");
let phxId = elem.attr("id");
if (!check(phxSession, { "found phx-session": (str) => !!str })) {
fail("session token not found");
}
if (!check(phxStatic, { "found phx-static": (str) => !!str })) {
fail("static token not found");
}
return { wsCsrfToken, phxSession, phxStatic, phxId };
}
export function checkStatus(response, status, msg = "request failed") {
if (
!check(response, {
"status OK": (res) => res.status.toString() === `${status}`,
})
) {
fail(`${msg} (Status: ${response.status.toString()}. Expected: ${status})`);
}
}
export function checkMessage(message, regex, msg = "unexpected ws message") {
if (!check(msg, { "ws msg OK": () => message.match(regex) })) {
console.log(message);
fail(`${msg} (Msg: ${message}. Expected: ${regex})`);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment