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]); }, [id, runId]);
const stepColumns: TableColumn<ScenarioRunStep>[] = [ 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', key: 'title',
header: t('runs.step_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 { BrowserService } from "./browser.service";
import { SessionModule } from "../session/session.module"; import { SessionModule } from "../session/session.module";
import { CodeExecutorModule } from "../code-executor/code-executor.module"; import { CodeExecutorModule } from "../code-executor/code-executor.module";
import { SnippetModule } from "../snippet/snippet.module";
@Module({ @Module({
imports: [SessionModule, CodeExecutorModule], imports: [SessionModule, CodeExecutorModule, SnippetModule],
controllers: [BrowserController], controllers: [BrowserController],
providers: [BrowserService], providers: [BrowserService],
exports: [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 { SessionService } from "../session/session.service";
import { CodeExecutorService } from "../code-executor/code-executor.service"; import { CodeExecutorService } from "../code-executor/code-executor.service";
import type { ExecResult } 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"; import type { Cookie } from "playwright";
export type { ExecResult } from "../code-executor/code-executor.service"; export type { ExecResult } from "../code-executor/code-executor.service";
@@ -30,6 +31,7 @@ export class BrowserService {
private readonly sessionContextService: SessionContextService, private readonly sessionContextService: SessionContextService,
private readonly sessionService: SessionService, private readonly sessionService: SessionService,
private readonly codeExecutor: CodeExecutorService, private readonly codeExecutor: CodeExecutorService,
private readonly snippetService: SnippetService,
) {} ) {}
private parseCookies(raw: string | null | undefined): Cookie[] { private parseCookies(raw: string | null | undefined): Cookie[] {
@@ -171,13 +173,25 @@ export class BrowserService {
const { page, context } = const { page, context } =
await this.getOrCreateNamedHandle(sessionName); await this.getOrCreateNamedHandle(sessionName);
this.logger.log(`[${label}] exec: using persistent context`); this.logger.log(`[${label}] exec: using persistent context`);
const snippets = await this.snippetService
.buildSnippetMap()
.catch(() => ({}) as Record<string, string>);
try { try {
if (url) { if (url) {
this.logger.log(`[${label}] exec: navigating to ${url}`); this.logger.log(`[${label}] exec: navigating to ${url}`);
await page.goto(url, { waitUntil: "networkidle" }); await page.goto(url, { waitUntil: "networkidle" });
} }
this.logger.log(`[${label}] exec: running user code`); 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`); this.logger.log(`[${label}] exec: done`);
return result; return result;
} catch (err) { } catch (err) {
@@ -186,6 +200,9 @@ export class BrowserService {
} }
// Anonymous — ephemeral browser // Anonymous — ephemeral browser
const snippetMap = await this.snippetService
.buildSnippetMap()
.catch(() => ({}) as Record<string, string>);
const browser = await chromium.launch({ const browser = await chromium.launch({
headless: true, headless: true,
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
@@ -200,7 +217,16 @@ export class BrowserService {
} }
this.logger.log(`[${label}] exec: running user code`); 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`); this.logger.log(`[${label}] exec: done`);
return result; return result;
} catch (err) { } catch (err) {