refactor(browser): make sessionName optional, add selector filter, deduplicate session setup

- sessionName is now optional in open/exec; skips session restore when omitted
- add selector param to open: returns outerHTML or textContent of matched element
- extract session restore logic into private setupSession() to remove duplication
- replace per-exception instanceof checks with single HttpException base class check
- embed label into InternalServerErrorException message instead of logging separately
- update MCP tool schemas and HTTP DTOs to reflect optional sessionName and new selector
- add integration tests: sessionless open/exec, selector, selector+readerMode
This commit is contained in:
2026-04-08 11:10:08 +03:00
parent f1dadc602a
commit f0437d9390
7 changed files with 109 additions and 86 deletions
+10 -1
View File
@@ -1,6 +1,15 @@
const mockElement = {
outerHTML: '<div id="mock">mock content</div>',
textContent: 'mock content',
};
export class JSDOM {
constructor(public html: string, public options?: any) {}
get window() {
return { document: {} };
return {
document: {
querySelector: (_selector: string) => mockElement,
},
};
}
}
+36 -7
View File
@@ -45,13 +45,6 @@ describe('BrowserController', () => {
await request(app.getHttpServer()).post('/open').send({}).expect(400);
});
it('returns 400 when sessionName is missing', async () => {
await request(app.getHttpServer())
.post('/open')
.send({ url: 'https://example.com' })
.expect(400);
});
it('returns 400 when url is missing', async () => {
await request(app.getHttpServer())
.post('/open')
@@ -65,6 +58,34 @@ describe('BrowserController', () => {
.send({ sessionName: 'no-such-session', url: 'https://example.com' })
.expect(404);
});
it('succeeds without a session (sessionless open)', async () => {
const res = await request(app.getHttpServer())
.post('/open')
.send({ url: 'https://example.com' })
.expect(201);
expect(res.body).toMatchObject({
url: expect.any(String),
title: expect.any(String),
content: expect.any(String),
});
});
it('returns only selector content when selector is provided', async () => {
const res = await request(app.getHttpServer())
.post('/open')
.send({ url: 'https://example.com', selector: '#mock' })
.expect(201);
expect(res.body.content).toBe('<div id="mock">mock content</div>');
});
it('returns selector text content in reader mode', async () => {
const res = await request(app.getHttpServer())
.post('/open')
.send({ url: 'https://example.com', selector: '#mock', readerMode: true })
.expect(201);
expect(res.body.content).toBe('mock content');
});
});
// ── POST /exec ─────────────────────────────────────────────────────────────
@@ -94,5 +115,13 @@ describe('BrowserController', () => {
.send({ sessionName: 'no-such-session', code: 'return 1;' })
.expect(404);
});
it('succeeds without a session (sessionless exec)', async () => {
const res = await request(app.getHttpServer())
.post('/exec')
.send({ code: 'return 42;' })
.expect(201);
expect(res.body).toEqual({ result: 42 });
});
});
});