fix: fix links in scenario service

This commit is contained in:
2026-04-14 21:11:35 +03:00
parent 5ec9fd0bc3
commit a71be39076
6 changed files with 50 additions and 45 deletions
+24 -23
View File
@@ -291,7 +291,7 @@ export function RunDetailPage() {
items={[ items={[
{ label: t('scenarios.title'), icon: <ClipboardList size={14} />, onClick: () => navigate('/scenarios') }, { label: t('scenarios.title'), icon: <ClipboardList size={14} />, onClick: () => navigate('/scenarios') },
{ {
label: scenario?.name ?? `#${id}`, label: scenario?.name ?? `${id}`,
onClick: () => navigate(`/scenarios/${id}`), onClick: () => navigate(`/scenarios/${id}`),
}, },
{ {
@@ -299,7 +299,7 @@ export function RunDetailPage() {
icon: <Activity size={14} />, icon: <Activity size={14} />,
onClick: () => navigate(`/scenarios/${id}/runs`), onClick: () => navigate(`/scenarios/${id}/runs`),
}, },
{ label: `#${runId}` }, { label: `${runId}` },
]} ]}
/> />
<AutoRefreshIndicator active={polling} pulseKey={pulseKey} error={!!error} onClick={manualRefresh} /> <AutoRefreshIndicator active={polling} pulseKey={pulseKey} error={!!error} onClick={manualRefresh} />
@@ -312,27 +312,28 @@ export function RunDetailPage() {
<Card> <Card>
<DescriptionList <DescriptionList
layout="grid" layout="grid"
items={[ items={(() => {
{ term: t('runs.field_id'), detail: <UuidBadge id={run.id} /> }, const items = [
{ { term: t('runs.field_id'), detail: <UuidBadge id={run.id} /> },
term: t('runs.field_status'), {
detail: ( term: t('runs.field_status'),
<Badge variant={RUN_STATUS_VARIANT[run.status]}> detail: (
{RUN_STATUS_LABEL[run.status]} <Badge variant={RUN_STATUS_VARIANT[run.status]}>
</Badge> {RUN_STATUS_LABEL[run.status]}
), </Badge>
}, ),
{ term: t('runs.field_created'), detail: <Timestamp value={run.createdAt} /> }, },
{ term: t('runs.field_updated'), detail: <Timestamp value={run.updatedAt} /> }, { term: t('runs.field_created'), detail: <Timestamp value={run.createdAt} /> },
...(run.environment ? [{ { term: t('runs.field_updated'), detail: <Timestamp value={run.updatedAt} /> },
term: 'Environment', ];
detail: <span style={{ color: 'var(--color-link)', cursor: 'pointer' }}>{run.environment.name}</span>, if (run.environment) {
}] : []), items.push({ term: 'Environment', detail: <Link to={`/environments/${run.environment.id}`} style={{ color: 'var(--color-link)' }}>{run.environment.name}</Link> });
...(run.session ? [{ }
term: 'Session', if (run.session) {
detail: <Link to={`/sessions/${run.session.id}`} style={{ color: 'var(--color-link)' }}>{run.session.sessionName}</Link>, items.push({ term: 'Session', detail: <Link to={`/sessions/${run.session.id}`} style={{ color: 'var(--color-link)' }}>{run.session.sessionName}</Link> });
}] : []), }
]} return items;
})()}
/> />
</Card> </Card>
@@ -559,7 +559,6 @@ export function ScenarioDetailPage() {
type="checkbox" type="checkbox"
checked={saveSessionFlag} checked={saveSessionFlag}
onChange={(e) => setSaveSessionFlag(e.target.checked)} onChange={(e) => setSaveSessionFlag(e.target.checked)}
disabled
/> />
<span>Save session</span> <span>Save session</span>
</label> </label>
@@ -222,7 +222,6 @@ export function ScenariosPage() {
type="checkbox" type="checkbox"
checked={saveSessionFlag} checked={saveSessionFlag}
onChange={(e) => setSaveSessionFlag(e.target.checked)} onChange={(e) => setSaveSessionFlag(e.target.checked)}
disabled
/> />
<span>Save session</span> <span>Save session</span>
</label> </label>
+4 -4
View File
@@ -8,10 +8,10 @@ import {
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
UpdateDateColumn, UpdateDateColumn,
} from "typeorm"; } from "typeorm";
import { ScenarioRunStepEntity } from "./scenario-run-step.entity";
import { ScenarioEntity } from "./scenario.entity";
import { EnvironmentEntity } from "../environment/environment.entity"; import { EnvironmentEntity } from "../environment/environment.entity";
import { SessionEntity } from "../session/session.entity"; import { SessionEntity } from "../session/session.entity";
import { ScenarioRunStepEntity } from "./scenario-run-step.entity";
import { ScenarioEntity } from "./scenario.entity";
export type RunStatus = "pending" | "in_progress" | "pass" | "fail"; export type RunStatus = "pending" | "in_progress" | "pass" | "fail";
@@ -23,8 +23,8 @@ export class ScenarioRunEntity {
@Column("text") @Column("text")
scenarioId: string; scenarioId: string;
@Column("text", { default: "" }) @Column("text", { nullable: true, default: null })
environmentId: string; environmentId: string | null;
@ManyToOne(() => ScenarioEntity, { onDelete: "CASCADE" }) @ManyToOne(() => ScenarioEntity, { onDelete: "CASCADE" })
@JoinColumn({ name: "scenarioId" }) @JoinColumn({ name: "scenarioId" })
@@ -7,6 +7,7 @@ import { chromium } from "playwright";
import { Repository } from "typeorm"; import { Repository } from "typeorm";
import type { ScriptLogger } from "../code-executor/code-executor.service"; import type { ScriptLogger } from "../code-executor/code-executor.service";
import { CodeExecutorService } from "../code-executor/code-executor.service"; import { CodeExecutorService } from "../code-executor/code-executor.service";
import { ExecContextBuilder } from "../code-executor/exec-context.builder";
import { traceStorage } from "../common/trace-context"; import { traceStorage } from "../common/trace-context";
import { TraceLogger } from "../common/trace-logger"; import { TraceLogger } from "../common/trace-logger";
import { EnvironmentData, EnvironmentEntity } from "../environment/environment.entity"; import { EnvironmentData, EnvironmentEntity } from "../environment/environment.entity";
@@ -170,7 +171,9 @@ export class ScenarioSchedulerService {
} }
const handle = this.runBrowsers.get(runId); const handle = this.runBrowsers.get(runId);
if (!handle) return; if (!handle) {
return;
}
const sessionName = `run-${runId}`; const sessionName = `run-${runId}`;
try { try {
@@ -184,7 +187,8 @@ export class ScenarioSchedulerService {
cookies.find((c) => c.name === "token")?.value ?? cookies.find((c) => c.name === "token")?.value ??
""; "";
await this.sessionService.upsert(sessionName, token, cookies, localStorage); const session = await this.sessionService.upsert(sessionName, token, cookies, localStorage);
await this.runRepo.update(runId, { sessionId: session.id });
this.sessionContextService.register( this.sessionContextService.register(
sessionName, sessionName,
handle.browser, handle.browser,
@@ -194,7 +198,7 @@ export class ScenarioSchedulerService {
// Remove from runBrowsers so closeBrowserHandle won't close it // Remove from runBrowsers so closeBrowserHandle won't close it
this.runBrowsers.delete(runId); this.runBrowsers.delete(runId);
this.logger.log( this.logger.log(
`Run #${runId}: browser preserved as session "${sessionName}"`, `Run #${runId}: browser preserved as session "${sessionName}" (${session.id})`,
); );
} catch (err) { } catch (err) {
this.logger.warn( this.logger.warn(
@@ -223,16 +227,17 @@ export class ScenarioSchedulerService {
const creds = this.runCredentials.get(stepRun.runId); const creds = this.runCredentials.get(stepRun.runId);
const snips = this.runSnippets.get(stepRun.runId); const snips = this.runSnippets.get(stepRun.runId);
const env = this.runEnvironments.get(stepRun.runId); const env = this.runEnvironments.get(stepRun.runId);
const { result: execOutput } = await this.codeExecutor.execute({ const execCtx = new ExecContextBuilder()
page, .page(page)
browser: context, .browser(context)
code: step.execCode, .code(step.execCode)
log: this.stepLogger(stepRun.id, stepRun.runId), .log(this.stepLogger(stepRun.id, stepRun.runId))
getStepOutput, .getStepOutput(getStepOutput)
credentials: creds, .credentials(creds)
environment: env, .environment(env)
snippets: snips, .snippets(snips)
}); .build();
const { result: execOutput } = await this.codeExecutor.execute(execCtx);
await this.passStepRun(stepRun, null, execOutput); await this.passStepRun(stepRun, null, execOutput);
} catch (err) { } catch (err) {
@@ -310,7 +315,8 @@ export class ScenarioSchedulerService {
], ],
}); });
if (remaining === 0) { if (remaining === 0) {
await this.closeBrowserHandle(stepRun.runId); // Don't close the browser here — maybePreserveSession will handle it
// (either preserving it if saveSession=true, or closing if false)
await this.runRepo.update(stepRun.runId, { status: "pass" }); await this.runRepo.update(stepRun.runId, { status: "pass" });
this.logger.log(`Run #${stepRun.runId} → pass (all steps passed)`); this.logger.log(`Run #${stepRun.runId} → pass (all steps passed)`);
} }
+2 -2
View File
@@ -322,7 +322,7 @@ export class ScenarioService {
await this.findOne(scenarioId); // 404 guard await this.findOne(scenarioId); // 404 guard
const run = await this.runRepo.findOne({ const run = await this.runRepo.findOne({
where: { id: runId, scenarioId }, where: { id: runId, scenarioId },
relations: ["stepRuns", "stepRuns.scenarioStep"], relations: ["stepRuns", "stepRuns.scenarioStep", "environment", "session"],
order: { stepRuns: { order: "ASC" } }, order: { stepRuns: { order: "ASC" } },
}); });
if (!run) if (!run)
@@ -387,7 +387,7 @@ export class ScenarioService {
return this.runRepo.findOne({ return this.runRepo.findOne({
where: { id: run.id }, where: { id: run.id },
relations: ["stepRuns"], relations: ["stepRuns", "environment", "session"],
order: { stepRuns: { order: "ASC" } }, order: { stepRuns: { order: "ASC" } },
}) as Promise<ScenarioRunEntity>; }) as Promise<ScenarioRunEntity>;
} }