// Selfhost-only (browser): guards two pieces of user feedback about the// API-keys experience on a self-hosted instance —//// 1. the copy-API-key buttons work even on a plain-HTTP (non-secure) origin,// and// 2. the API keys page is reachable from the main sidebar.//// Selfhost is the right target for (1): a self-hosted console is typically// served over plain HTTP on a LAN host/IP — a NON-secure origin, where the// browser does not expose `navigator.clipboard`. There the copy buttons fall// back to `document.execCommand("copy")` (see @executor-js/react `lib/clipboard`).// The harness runs on http://localhost, which IS a secure context, so the copy// test drops `navigator.clipboard` to reproduce the real deployment and records// what the page copies through the fallback.import { randomBytes } from "node:crypto"; import { expect } from "@effect/vitest";import { Effect } from "effect";import { AccountHttpApi } from "@executor-js/api"; import { scenario } from "../src/scenario";import { Api, Browser, Target } from "../src/services";import { visit } from "../src/surfaces/browser"; declare global { interface Window { // The copy test stashes the text the page copied (via the execCommand // fallback) here, so it can be read back out of the browser context. __e2eCopied?: Array<string>; }} scenario( "API keys · the page is reachable from the main sidebar", {}, Effect.gen(function* () { const target = yield* Target; const browser = yield* Browser; const identity = yield* target.newIdentity(); yield* browser.session(identity, async ({ page, step }) => { await step("Land on the dashboard", async () => { await visit(page, "/"); // The shared shell's main nav lists the workspace sections. await page.locator("nav").getByRole("link", { name: "Integrations" }).first().waitFor(); }); await step("The main sidebar links straight to API keys", async () => { // Feedback: "the api keys link should be in the main sidebar." Today the // link lives only in the account dropdown at the bottom of the sidebar // (a closed popover that isn't even mounted), so there is no API-keys // link in the main <nav>. Scoping to <nav> is what makes this the repro: // it ignores the dropdown and asserts a first-class sidebar item. The // failure message lists the items the sidebar actually has today. const navLinks = await page.locator("nav").getByRole("link").allInnerTexts(); expect(navLinks, "API keys should be a first-class item in the main sidebar nav").toContain( "API keys", ); }); }); }),); scenario( "API keys · the copy button copies a new key on a plain-HTTP self-host", {}, Effect.gen(function* () { const target = yield* Target; const browser = yield* Browser; const { client: apiClient } = yield* Api; const identity = yield* target.newIdentity(); const client = yield* apiClient(AccountHttpApi, identity); // Selfhost is single-tenant, so name the key uniquely and revoke it after. const keyName = `copy-repro-${randomBytes(3).toString("hex")}`; yield* browser .session(identity, async ({ page, step }) => { // Recreate the real self-host deployment: a plain-HTTP, non-secure // origin. There the browser does not expose `navigator.clipboard`, so // the page must fall back to `document.execCommand("copy")` (the // universal insecure-context copy path). We drop the Clipboard API // exactly as a non-secure origin does, and record what the page copies // through the fallback so we can assert the key actually reached the // clipboard. (The harness itself runs on http://localhost, which IS a // secure context — without this it would mask the bug entirely.) await page.addInitScript(() => { Object.defineProperty(navigator, "clipboard", { configurable: true, get: () => undefined, }); const copied: Array<string> = []; window.__e2eCopied = copied; const exec = document.execCommand?.bind(document); document.execCommand = (command, ...rest) => { if (String(command).toLowerCase() === "copy") { // execCommand("copy") copies the current selection — capture it. const selected = window.getSelection ? String(window.getSelection()) : ""; const active = document.activeElement; const fromField = active && "value" in active && typeof active.value === "string" ? active.value : ""; copied.push(selected || fromField); } return exec ? exec(command, ...rest) : false; }; }); await step("Open the API keys page", async () => { await visit(page, "/api-keys"); await page.getByRole("heading", { name: "API keys", exact: true }).waitFor(); }); await step("Create a new key", async () => { await page.getByRole("button", { name: "New key" }).click(); const dialog = page.getByRole("dialog"); await dialog.getByLabel("Name").fill(keyName); await dialog.getByRole("button", { name: "Create key" }).click(); // The one-time secret panel renders once the key exists. await dialog.getByText("It is only shown once").waitFor(); }); await step("Copy the key with the copy button", async () => { const dialog = page.getByRole("dialog"); // The exact secret the copy button should place on the clipboard. const keyValue = await dialog.locator("input[readonly]").first().inputValue(); expect(keyValue, "the one-time secret is shown to copy").not.toBe(""); // The copy button must place the key on the clipboard even here, where // navigator.clipboard is unavailable — it falls back to // execCommand("copy"), which has to survive the dialog's focus trap. await dialog.getByRole("button", { name: "Copy" }).first().click(); const copied = await page.evaluate(() => window.__e2eCopied ?? []); expect(copied, "clicking copy should put the key on the clipboard").toContain(keyValue); }); }) .pipe( // Revoke the key whether the copy assertion passed or failed, so the // shared single-tenant instance is left clean. Effect.ensuring( Effect.gen(function* () { const list = yield* client.account.listApiKeys(); const mine = list.apiKeys.find((key) => key.name === keyName); if (mine) yield* client.account.revokeApiKey({ params: { apiKeyId: mine.id } }); }).pipe(Effect.ignore), ), ); }),); scenario( "API keys · a copy that can't reach the clipboard surfaces an error toast", {}, Effect.gen(function* () { const target = yield* Target; const browser = yield* Browser; const { client: apiClient } = yield* Api; const identity = yield* target.newIdentity(); const client = yield* apiClient(AccountHttpApi, identity); const keyName = `copy-fail-${randomBytes(3).toString("hex")}`; yield* browser .session(identity, async ({ page, step }) => { // Simulate a context where the copy genuinely can't happen: no // navigator.clipboard (non-secure origin) AND execCommand("copy") // refuses (some browsers/extensions block it). The copy then truly // fails, and the button must say so rather than silently doing nothing. await page.addInitScript(() => { Object.defineProperty(navigator, "clipboard", { configurable: true, get: () => undefined, }); document.execCommand = (command) => String(command).toLowerCase() !== "copy"; }); await step("Create a new key", async () => { await visit(page, "/api-keys"); await page.getByRole("button", { name: "New key" }).click(); const dialog = page.getByRole("dialog"); await dialog.getByLabel("Name").fill(keyName); await dialog.getByRole("button", { name: "Create key" }).click(); await dialog.getByText("It is only shown once").waitFor(); }); await step("A failed copy tells the user instead of failing silently", async () => { await page.getByRole("dialog").getByRole("button", { name: "Copy" }).first().click(); await page.getByText("Failed to copy to clipboard").waitFor(); }); }) .pipe( Effect.ensuring( Effect.gen(function* () { const list = yield* client.account.listApiKeys(); const mine = list.apiKeys.find((key) => key.name === keyName); if (mine) yield* client.account.revokeApiKey({ params: { apiKeyId: mine.id } }); }).pipe(Effect.ignore), ), ); }),);
// Selfhost-only (browser): guards two pieces of user feedback about the
// API-keys experience on a self-hosted instance —
//
// 1. the copy-API-key buttons work even on a plain-HTTP (non-secure) origin,
// and
// 2. the API keys page is reachable from the main sidebar.
//
// Selfhost is the right target for (1): a self-hosted console is typically
// served over plain HTTP on a LAN host/IP — a NON-secure origin, where the
// browser does not expose `navigator.clipboard`. There the copy buttons fall
// back to `document.execCommand("copy")` (see @executor-js/react `lib/clipboard`).
// The harness runs on http://localhost, which IS a secure context, so the copy
// test drops `navigator.clipboard` to reproduce the real deployment and records
// what the page copies through the fallback.
import { randomBytes } from "node:crypto";
import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { AccountHttpApi } from "@executor-js/api";
import { scenario } from "../src/scenario";
import { Api, Browser, Target } from "../src/services";
import { visit } from "../src/surfaces/browser";
declare global {
interface Window {
// The copy test stashes the text the page copied (via the execCommand
// fallback) here, so it can be read back out of the browser context.
__e2eCopied?: Array<string>;
}
}
scenario(
"API keys · the page is reachable from the main sidebar",
{},
Effect.gen(function* () {
const target = yield* Target;
const browser = yield* Browser;
const identity = yield* target.newIdentity();
yield* browser.session(identity, async ({ page, step }) => {
await step("Land on the dashboard", async () => {
await visit(page, "/");
// The shared shell's main nav lists the workspace sections.
await page.locator("nav").getByRole("link", { name: "Integrations" }).first().waitFor();
});
await step("The main sidebar links straight to API keys", async () => {
// Feedback: "the api keys link should be in the main sidebar." Today the
// link lives only in the account dropdown at the bottom of the sidebar
// (a closed popover that isn't even mounted), so there is no API-keys
// link in the main <nav>. Scoping to <nav> is what makes this the repro:
// it ignores the dropdown and asserts a first-class sidebar item. The
// failure message lists the items the sidebar actually has today.
const navLinks = await page.locator("nav").getByRole("link").allInnerTexts();
expect(navLinks, "API keys should be a first-class item in the main sidebar nav").toContain(
"API keys",
);
});
});
}),
);
scenario(
"API keys · the copy button copies a new key on a plain-HTTP self-host",
{},
Effect.gen(function* () {
const target = yield* Target;
const browser = yield* Browser;
const { client: apiClient } = yield* Api;
const identity = yield* target.newIdentity();
const client = yield* apiClient(AccountHttpApi, identity);
// Selfhost is single-tenant, so name the key uniquely and revoke it after.
const keyName = `copy-repro-${randomBytes(3).toString("hex")}`;
yield* browser
.session(identity, async ({ page, step }) => {
// Recreate the real self-host deployment: a plain-HTTP, non-secure
// origin. There the browser does not expose `navigator.clipboard`, so
// the page must fall back to `document.execCommand("copy")` (the
// universal insecure-context copy path). We drop the Clipboard API
// exactly as a non-secure origin does, and record what the page copies
// through the fallback so we can assert the key actually reached the
// clipboard. (The harness itself runs on http://localhost, which IS a
// secure context — without this it would mask the bug entirely.)
await page.addInitScript(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
get: () => undefined,
});
const copied: Array<string> = [];
window.__e2eCopied = copied;
const exec = document.execCommand?.bind(document);
document.execCommand = (command, ...rest) => {
if (String(command).toLowerCase() === "copy") {
// execCommand("copy") copies the current selection — capture it.
const selected = window.getSelection ? String(window.getSelection()) : "";
const active = document.activeElement;
const fromField =
active && "value" in active && typeof active.value === "string" ? active.value : "";
copied.push(selected || fromField);
}
return exec ? exec(command, ...rest) : false;
};
});
await step("Open the API keys page", async () => {
await visit(page, "/api-keys");
await page.getByRole("heading", { name: "API keys", exact: true }).waitFor();
});
await step("Create a new key", async () => {
await page.getByRole("button", { name: "New key" }).click();
const dialog = page.getByRole("dialog");
await dialog.getByLabel("Name").fill(keyName);
await dialog.getByRole("button", { name: "Create key" }).click();
// The one-time secret panel renders once the key exists.
await dialog.getByText("It is only shown once").waitFor();
});
await step("Copy the key with the copy button", async () => {
const dialog = page.getByRole("dialog");
// The exact secret the copy button should place on the clipboard.
const keyValue = await dialog.locator("input[readonly]").first().inputValue();
expect(keyValue, "the one-time secret is shown to copy").not.toBe("");
// The copy button must place the key on the clipboard even here, where
// navigator.clipboard is unavailable — it falls back to
// execCommand("copy"), which has to survive the dialog's focus trap.
await dialog.getByRole("button", { name: "Copy" }).first().click();
const copied = await page.evaluate(() => window.__e2eCopied ?? []);
expect(copied, "clicking copy should put the key on the clipboard").toContain(keyValue);
});
})
.pipe(
// Revoke the key whether the copy assertion passed or failed, so the
// shared single-tenant instance is left clean.
Effect.ensuring(
Effect.gen(function* () {
const list = yield* client.account.listApiKeys();
const mine = list.apiKeys.find((key) => key.name === keyName);
if (mine) yield* client.account.revokeApiKey({ params: { apiKeyId: mine.id } });
}).pipe(Effect.ignore),
),
);
}),
);
scenario(
"API keys · a copy that can't reach the clipboard surfaces an error toast",
{},
Effect.gen(function* () {
const target = yield* Target;
const browser = yield* Browser;
const { client: apiClient } = yield* Api;
const identity = yield* target.newIdentity();
const client = yield* apiClient(AccountHttpApi, identity);
const keyName = `copy-fail-${randomBytes(3).toString("hex")}`;
yield* browser
.session(identity, async ({ page, step }) => {
// Simulate a context where the copy genuinely can't happen: no
// navigator.clipboard (non-secure origin) AND execCommand("copy")
// refuses (some browsers/extensions block it). The copy then truly
// fails, and the button must say so rather than silently doing nothing.
await page.addInitScript(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
get: () => undefined,
});
document.execCommand = (command) => String(command).toLowerCase() !== "copy";
});
await step("Create a new key", async () => {
await visit(page, "/api-keys");
await page.getByRole("button", { name: "New key" }).click();
const dialog = page.getByRole("dialog");
await dialog.getByLabel("Name").fill(keyName);
await dialog.getByRole("button", { name: "Create key" }).click();
await dialog.getByText("It is only shown once").waitFor();
});
await step("A failed copy tells the user instead of failing silently", async () => {
await page.getByRole("dialog").getByRole("button", { name: "Copy" }).first().click();
await page.getByText("Failed to copy to clipboard").waitFor();
});
})
.pipe(
Effect.ensuring(
Effect.gen(function* () {
const list = yield* client.account.listApiKeys();
const mine = list.apiKeys.find((key) => key.name === keyName);
if (mine) yield* client.account.revokeApiKey({ params: { apiKeyId: mine.id } });
}).pipe(Effect.ignore),
),
);
}),
);