Skip to content
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
66 changes: 0 additions & 66 deletions dashboard/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ const playwrightStatus = document.querySelector("#playwright-status");
const playwrightGuidance = document.querySelector("#playwright-guidance");
const playwrightCheck = document.querySelector("#playwright-check");
const playwrightConnect = document.querySelector("#playwright-connect");
const frontendStatusBadge = document.querySelector("#frontend-status");
const frontendGuidance = document.querySelector("#frontend-guidance");
const frontendStart = document.querySelector("#frontend-start");
const frontendStop = document.querySelector("#frontend-stop");
const onboardingStatus = document.querySelector("#onboarding-status");
const onboardingOrganization = document.querySelector(
"#onboarding-organization"
Expand Down Expand Up @@ -576,64 +572,6 @@ async function resumeAfterConnect(status) {
}
}

function renderFrontendStatus(status) {
frontendStatusBadge.textContent = !status.configured
? "Not configured"
: status.healthy
? "Ready"
: status.running
? "Starting…"
: "Stopped";
frontendStart.disabled =
!status.configured || status.running || status.healthy;
frontendStop.disabled = !status.running;
frontendGuidance.textContent = status.error
? status.error
: status.healthy
? `React is answering at ${status.healthUrl}. Playwright authentication and live tests reuse it.`
: status.running
? "React is compiling. The first start can take several minutes; this updates automatically."
: status.lastResult && !status.lastResult.ok
? status.lastResult.message
: "Start the profile-defined React frontend ahead of time so Playwright authentication and live tests reuse it instead of waiting for a cold compile.";
}

async function refreshFrontendStatus() {
try {
renderFrontendStatus(await fetchJson("/api/frontend/status"));
} catch (error) {
frontendGuidance.textContent = error.message;
}
}

async function startFrontend() {
try {
renderFrontendStatus(
await fetchJson("/api/frontend/start", { method: "POST" })
);
} catch {
await refreshFrontendStatus();
}
}

frontendStart.addEventListener("click", () => {
formError.textContent = "";
frontendStart.disabled = true;
void startFrontend();
});

frontendStop.addEventListener("click", async () => {
frontendStop.disabled = true;
try {
renderFrontendStatus(
await fetchJson("/api/frontend/stop", { method: "POST" })
);
} catch (error) {
formError.textContent = error.message;
await refreshFrontendStatus();
}
});

async function refreshPlaywrightStatus() {
try {
renderPlaywrightStatus(await fetchJson("/api/playwright/status"));
Expand Down Expand Up @@ -1433,7 +1371,6 @@ connectRetryLiveTestButton.addEventListener("click", async () => {
connectRetryLiveTestButton.disabled = true;
try {
pendingRetryAfterConnectJobId = activeJob?.id ?? null;
await startFrontend();
renderPlaywrightStatus(
await fetchJson("/api/playwright/connect", { method: "POST" })
);
Expand Down Expand Up @@ -1510,7 +1447,6 @@ playwrightConnect.addEventListener("click", async () => {
formError.textContent = "";
playwrightConnect.disabled = true;
try {
await startFrontend();
renderPlaywrightStatus(
await fetchJson("/api/playwright/connect", { method: "POST" })
);
Expand Down Expand Up @@ -1550,11 +1486,9 @@ onboardingSave.addEventListener("click", async () => {
refresh();
refreshMetrics();
refreshPlaywrightStatus();
refreshFrontendStatus();
refreshPlaywrightEvidence();
refreshOnboarding();
setInterval(refresh, 1000);
setInterval(refreshMetrics, 5000);
setInterval(refreshPlaywrightStatus, 5000);
setInterval(refreshFrontendStatus, 5000);
setInterval(refreshPlaywrightEvidence, 10000);
12 changes: 0 additions & 12 deletions dashboard/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,6 @@ <h2>Playwright authentication</h2>
</div>
</section>

<section class="panel connection-panel">
<div class="section-heading">
<h2>React frontend</h2>
<span id="frontend-status" class="badge">Checking…</span>
</div>
<p id="frontend-guidance" class="guidance">Start the profile-defined React frontend ahead of time so Playwright authentication and live tests reuse it instead of waiting for a cold compile.</p>
<div class="button-row">
<button id="frontend-start" type="button">Start React now</button>
<button id="frontend-stop" type="button" class="secondary">Stop React</button>
</div>
</section>

<section class="panel">
<div class="section-heading">
<h2>Repository onboarding</h2>
Expand Down
155 changes: 0 additions & 155 deletions dashboard/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2186,77 +2186,6 @@ function playwrightAuthenticationConfig(repository) {
};
}

function frontendStartConfig(repository) {
const profile = loadRepositoryProfile(repository);
const frontend = profile.applications?.frontend ?? {};
const command =
typeof frontend.command === "string" ? frontend.command.trim() : "";
if (!command) {
throw new Error("repository profile must configure applications.frontend.command");
}
return {
command,
healthUrl: validatedManualLocalhostUrl(frontend.healthUrl),
workingDirectory: resolveContainedPath(
repository,
frontend.workingDirectory ?? ".",
"applications.frontend.workingDirectory",
{ mustExist: true }
)
};
}

async function frontendIsHealthy(healthUrl) {
try {
const response = await fetch(healthUrl, {
signal: AbortSignal.timeout(2000)
});
await response.body?.cancel();
return response.status < 500;
} catch {
return false;
}
}

function stopProcessTree(child) {
if (!child || child.exitCode !== null || child.signalCode !== null) {
return;
}
if (process.platform === "win32" && child.pid) {
spawnSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
stdio: "ignore",
windowsHide: true
});
return;
}
child.kill();
}

async function frontendStatus(repository, connection) {
let healthUrl = "";
try {
({ healthUrl } = frontendStartConfig(repository));
} catch (error) {
return {
configured: false,
running: Boolean(connection.handle),
healthy: false,
owned: Boolean(connection.handle),
error: error.message,
lastResult: connection.lastResult
};
}
return {
configured: true,
running: Boolean(connection.handle),
healthy: await frontendIsHealthy(healthUrl),
owned: Boolean(connection.handle),
healthUrl,
error: "",
lastResult: connection.lastResult
};
}

function playwrightAuthenticationStatus(repository, connection) {
let configuration;
try {
Expand Down Expand Up @@ -2434,7 +2363,6 @@ export function createDashboardServer({
const completedJobs = [];
const artifactDirectories = new Set();
const playwrightConnection = { handle: null, lastResult: null };
const frontendConnection = { handle: null, lastResult: null };
const playwrightArtifacts = new Map();
const cacheContext = createCacheContext(resolvedRepository);
const metricsPath = cacheContext
Expand Down Expand Up @@ -3714,87 +3642,6 @@ export function createDashboardServer({
return;
}

if (
request.method === "GET" &&
requestUrl.pathname === "/api/frontend/status"
) {
sendJson(
response,
200,
await frontendStatus(resolvedRepository, frontendConnection)
);
return;
}

if (
request.method === "POST" &&
requestUrl.pathname === "/api/frontend/start"
) {
let configuration;
try {
configuration = frontendStartConfig(resolvedRepository);
} catch (error) {
sendJson(response, 400, { error: error.message });
return;
}
if (frontendConnection.handle) {
sendJson(response, 409, { error: "FixLab already started the frontend" });
return;
}
if (await frontendIsHealthy(configuration.healthUrl)) {
sendJson(response, 200, {
...(await frontendStatus(resolvedRepository, frontendConnection)),
started: false
});
return;
}
frontendConnection.lastResult = null;
const child = spawn(configuration.command, {
cwd: configuration.workingDirectory,
shell: true,
stdio: "ignore",
windowsHide: true
});
frontendConnection.handle = child;
child.once("error", (error) => {
frontendConnection.lastResult = {
ok: false,
message: error.message,
finishedAt: new Date().toISOString()
};
frontendConnection.handle = null;
});
child.once("close", (code) => {
if (frontendConnection.handle === child) {
frontendConnection.handle = null;
}
frontendConnection.lastResult = {
ok: code === 0,
message: `Frontend process exited with code ${code ?? "unknown"}.`,
finishedAt: new Date().toISOString()
};
});
sendJson(response, 202, {
...(await frontendStatus(resolvedRepository, frontendConnection)),
started: true
});
return;
}

if (
request.method === "POST" &&
requestUrl.pathname === "/api/frontend/stop"
) {
const child = frontendConnection.handle;
frontendConnection.handle = null;
stopProcessTree(child);
sendJson(response, 200, {
...(await frontendStatus(resolvedRepository, frontendConnection)),
stopped: Boolean(child)
});
return;
}

if (request.method === "GET" && staticFiles.has(requestUrl.pathname)) {
const fileName = staticFiles.get(requestUrl.pathname);
const filePath = join(publicDirectory, fileName);
Expand Down Expand Up @@ -3846,8 +3693,6 @@ export function createDashboardServer({
) {
playwrightConnection.handle.kill();
}
stopProcessTree(frontendConnection.handle);
frontendConnection.handle = null;
for (const directory of artifactDirectories) {
removeArtifactDirectory(directory);
}
Expand Down
8 changes: 0 additions & 8 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,6 @@ runs the same configured command, waits for you to sign in, and retries the
live test automatically only after the command exits successfully. Sign-in
always stays with you; FixLab never handles credentials.

The **React frontend** panel starts the profile's `applications.frontend.command`
in `applications.frontend.workingDirectory` ahead of time and watches
`healthUrl`. Connect Playwright starts it automatically, so authentication and
live tests reuse a warm server instead of waiting for a cold compile. FixLab
stops only the process it started, when you click **Stop React** or the
dashboard exits, and never touches a server that was already running. A
Playwright config that sets `reuseExistingServer` picks up the running server.

The dashboard remains **Not ready** until the profile also includes a frontend

The dashboard remains **Not ready** until the profile also includes a frontend **Not ready** until the profile also includes a frontendstartup command, loopback health URL, Playwright test command, explicit
Expand Down
15 changes: 0 additions & 15 deletions test/dashboard-auth-resume.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,6 @@ test("connect-then-retry starts the profile authentication through the API", ()
assert.match(handler, /method: "POST"/u);
});

test("connecting Playwright starts the frontend first", () => {
for (const marker of [
'playwrightConnect.addEventListener("click"',
'connectRetryLiveTestButton.addEventListener("click"'
]) {
const handler = appSource.slice(appSource.indexOf(marker));
assert.ok(
handler.indexOf("await startFrontend()") <
handler.indexOf("/api/playwright/connect"),
marker
);
}
assert.match(appSource, /\/api\/frontend\/start/u);
});

test("the live test is retried only after successful authentication", () => {
const start = appSource.indexOf("async function resumeAfterConnect");
const body = appSource.slice(
Expand Down
Loading