fix(browser): inject snippets into exec_code context; fix(ui): step order starts at 1 in run detail

- BrowserService.exec() now builds snippet map via SnippetService and passes
  it to CodeExecutorService.execute() for both named-session and anonymous paths
- SnippetModule added to BrowserModule imports to wire the dependency
- RunDetailPage step order column renders s.order + 1 (was 0-based)
This commit is contained in:
2026-04-14 16:28:31 +03:00
parent 310997c3ae
commit d34ea9e943
3 changed files with 31 additions and 4 deletions
+1 -1
View File
@@ -154,7 +154,7 @@ export function RunDetailPage() {
}, [id, runId]);
const stepColumns: TableColumn<ScenarioRunStep>[] = [
{ key: 'order', header: t('runs.step_order'), render: (s) => s.order, width: 50 },
{ key: 'order', header: t('runs.step_order'), render: (s) => s.order + 1, width: 50 },
{
key: 'title',
header: t('runs.step_title'),
+2 -1
View File
@@ -3,9 +3,10 @@ import { BrowserController } from "./browser.controller";
import { BrowserService } from "./browser.service";
import { SessionModule } from "../session/session.module";
import { CodeExecutorModule } from "../code-executor/code-executor.module";
import { SnippetModule } from "../snippet/snippet.module";
@Module({
imports: [SessionModule, CodeExecutorModule],
imports: [SessionModule, CodeExecutorModule, SnippetModule],
controllers: [BrowserController],
providers: [BrowserService],
exports: [BrowserService],
+28 -2
View File
@@ -12,6 +12,7 @@ import { SessionContextService } from "../session/session-context.service";
import { SessionService } from "../session/session.service";
import { CodeExecutorService } from "../code-executor/code-executor.service";
import type { ExecResult } from "../code-executor/code-executor.service";
import { SnippetService } from "../snippet/snippet.service";
import type { Cookie } from "playwright";
export type { ExecResult } from "../code-executor/code-executor.service";
@@ -30,6 +31,7 @@ export class BrowserService {
private readonly sessionContextService: SessionContextService,
private readonly sessionService: SessionService,
private readonly codeExecutor: CodeExecutorService,
private readonly snippetService: SnippetService,
) {}
private parseCookies(raw: string | null | undefined): Cookie[] {
@@ -171,13 +173,25 @@ export class BrowserService {
const { page, context } =
await this.getOrCreateNamedHandle(sessionName);
this.logger.log(`[${label}] exec: using persistent context`);
const snippets = await this.snippetService
.buildSnippetMap()
.catch(() => ({}) as Record<string, string>);
try {
if (url) {
this.logger.log(`[${label}] exec: navigating to ${url}`);
await page.goto(url, { waitUntil: "networkidle" });
}
this.logger.log(`[${label}] exec: running user code`);
const result = await this.codeExecutor.execute(page, context, code);
const result = await this.codeExecutor.execute(
page,
context,
code,
undefined,
undefined,
undefined,
undefined,
snippets,
);
this.logger.log(`[${label}] exec: done`);
return result;
} catch (err) {
@@ -186,6 +200,9 @@ export class BrowserService {
}
// Anonymous — ephemeral browser
const snippetMap = await this.snippetService
.buildSnippetMap()
.catch(() => ({}) as Record<string, string>);
const browser = await chromium.launch({
headless: true,
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
@@ -200,7 +217,16 @@ export class BrowserService {
}
this.logger.log(`[${label}] exec: running user code`);
const result = await this.codeExecutor.execute(page, context, code);
const result = await this.codeExecutor.execute(
page,
context,
code,
undefined,
undefined,
undefined,
undefined,
snippetMap,
);
this.logger.log(`[${label}] exec: done`);
return result;
} catch (err) {