1515 */
1616
1717import { renderModalStates } from './tab.js' ;
18+ import { mergeExpectations } from './schemas/expectation.js' ;
1819
1920import type { Tab , TabSnapshot } from './tab.js' ;
2021import type { ImageContent , TextContent } from '@modelcontextprotocol/sdk/types.js' ;
2122import type { Context } from './context.js' ;
23+ import type { ExpectationOptions } from './schemas/expectation.js' ;
2224
2325export class Response {
2426 private _result : string [ ] = [ ] ;
@@ -28,15 +30,17 @@ export class Response {
2830 private _includeSnapshot = false ;
2931 private _includeTabs = false ;
3032 private _tabSnapshot : TabSnapshot | undefined ;
33+ private _expectation : NonNullable < ExpectationOptions > ;
3134
3235 readonly toolName : string ;
3336 readonly toolArgs : Record < string , any > ;
3437 private _isError : boolean | undefined ;
3538
36- constructor ( context : Context , toolName : string , toolArgs : Record < string , any > ) {
39+ constructor ( context : Context , toolName : string , toolArgs : Record < string , any > , expectation ?: ExpectationOptions ) {
3740 this . _context = context ;
3841 this . toolName = toolName ;
3942 this . toolArgs = toolArgs ;
43+ this . _expectation = mergeExpectations ( toolName , expectation ) ;
4044 }
4145
4246 addResult ( result : string ) {
@@ -83,8 +87,16 @@ export class Response {
8387 async finish ( ) {
8488 // All the async snapshotting post-action is happening here.
8589 // Everything below should race against modal states.
86- if ( this . _includeSnapshot && this . _context . currentTab ( ) )
87- this . _tabSnapshot = await this . _context . currentTabOrDie ( ) . captureSnapshot ( ) ;
90+ if ( ( this . _includeSnapshot || this . _expectation . includeSnapshot ) && this . _context . currentTab ( ) ) {
91+ const options = this . _expectation . snapshotOptions ;
92+ if ( options ?. selector ) {
93+ // TODO: Implement partial snapshot capture based on selector
94+ // For now, capture full snapshot
95+ this . _tabSnapshot = await this . _context . currentTabOrDie ( ) . captureSnapshot ( ) ;
96+ } else {
97+ this . _tabSnapshot = await this . _context . currentTabOrDie ( ) . captureSnapshot ( ) ;
98+ }
99+ }
88100 for ( const tab of this . _context . tabs ( ) )
89101 await tab . updateTitle ( ) ;
90102 }
@@ -103,25 +115,28 @@ export class Response {
103115 response . push ( '' ) ;
104116 }
105117
106- // Add code if it exists.
107- if ( this . _code . length ) {
118+ // Add code if it exists and expectation allows it .
119+ if ( this . _code . length && this . _expectation . includeCode ) {
108120 response . push ( `### Ran Playwright code
109121\`\`\`js
110122${ this . _code . join ( '\n' ) }
111123\`\`\`` ) ;
112124 response . push ( '' ) ;
113125 }
114126
115- // List browser tabs.
116- if ( this . _includeSnapshot || this . _includeTabs )
117- response . push ( ...renderTabsMarkdown ( this . _context . tabs ( ) , this . _includeTabs ) ) ;
127+ // List browser tabs based on expectation.
128+ const shouldIncludeTabs = this . _expectation . includeTabs || this . _includeTabs ;
129+ const shouldIncludeSnapshot = this . _expectation . includeSnapshot || this . _includeSnapshot ;
130+
131+ if ( shouldIncludeSnapshot || shouldIncludeTabs )
132+ response . push ( ...renderTabsMarkdown ( this . _context . tabs ( ) , shouldIncludeTabs ) ) ;
118133
119- // Add snapshot if provided.
120- if ( this . _tabSnapshot ?. modalStates . length ) {
134+ // Add snapshot if provided and expectation allows it .
135+ if ( shouldIncludeSnapshot && this . _tabSnapshot ?. modalStates . length ) {
121136 response . push ( ...renderModalStates ( this . _context , this . _tabSnapshot . modalStates ) ) ;
122137 response . push ( '' ) ;
123- } else if ( this . _tabSnapshot ) {
124- response . push ( renderTabSnapshot ( this . _tabSnapshot ) ) ;
138+ } else if ( shouldIncludeSnapshot && this . _tabSnapshot ) {
139+ response . push ( this . renderFilteredTabSnapshot ( this . _tabSnapshot ) ) ;
125140 response . push ( '' ) ;
126141 }
127142
@@ -138,6 +153,73 @@ ${this._code.join('\n')}
138153
139154 return { content, isError : this . _isError } ;
140155 }
156+
157+ private renderFilteredTabSnapshot ( tabSnapshot : TabSnapshot ) : string {
158+ const lines : string [ ] = [ ] ;
159+ const consoleOptions = this . _expectation . consoleOptions ;
160+
161+ // Include console messages based on expectation
162+ if ( this . _expectation . includeConsole && tabSnapshot . consoleMessages . length ) {
163+ const filteredMessages = this . filterConsoleMessages ( tabSnapshot . consoleMessages , consoleOptions ) ;
164+ if ( filteredMessages . length ) {
165+ lines . push ( `### New console messages` ) ;
166+ for ( const message of filteredMessages )
167+ lines . push ( `- ${ trim ( message . toString ( ) , 100 ) } ` ) ;
168+ lines . push ( '' ) ;
169+ }
170+ }
171+
172+ // Include downloads based on expectation
173+ if ( this . _expectation . includeDownloads && tabSnapshot . downloads . length ) {
174+ lines . push ( `### Downloads` ) ;
175+ for ( const entry of tabSnapshot . downloads ) {
176+ if ( entry . finished )
177+ lines . push ( `- Downloaded file ${ entry . download . suggestedFilename ( ) } to ${ entry . outputFile } ` ) ;
178+ else
179+ lines . push ( `- Downloading file ${ entry . download . suggestedFilename ( ) } ...` ) ;
180+ }
181+ lines . push ( '' ) ;
182+ }
183+
184+ lines . push ( `### Page state` ) ;
185+ lines . push ( `- Page URL: ${ tabSnapshot . url } ` ) ;
186+ lines . push ( `- Page Title: ${ tabSnapshot . title } ` ) ;
187+ lines . push ( `- Page Snapshot:` ) ;
188+ lines . push ( '```yaml' ) ;
189+
190+ // Apply snapshot format and length restrictions
191+ let snapshot = tabSnapshot . ariaSnapshot ;
192+ const snapshotOptions = this . _expectation . snapshotOptions ;
193+
194+ if ( snapshotOptions ?. maxLength && snapshot . length > snapshotOptions . maxLength ) {
195+ snapshot = snapshot . slice ( 0 , snapshotOptions . maxLength ) + '...' ;
196+ }
197+
198+ lines . push ( snapshot ) ;
199+ lines . push ( '```' ) ;
200+
201+ return lines . join ( '\n' ) ;
202+ }
203+
204+ private filterConsoleMessages ( messages : any [ ] , options ?: NonNullable < ExpectationOptions > [ 'consoleOptions' ] ) : any [ ] {
205+ let filtered = messages ;
206+
207+ // Filter by levels if specified
208+ if ( options ?. levels && options . levels . length > 0 ) {
209+ filtered = filtered . filter ( msg => {
210+ const level = msg . type || 'log' ;
211+ return options . levels ! . includes ( level ) ;
212+ } ) ;
213+ }
214+
215+ // Limit number of messages
216+ const maxMessages = options ?. maxMessages ?? 10 ;
217+ if ( filtered . length > maxMessages ) {
218+ filtered = filtered . slice ( 0 , maxMessages ) ;
219+ }
220+
221+ return filtered ;
222+ }
141223}
142224
143225function renderTabSnapshot ( tabSnapshot : TabSnapshot ) : string {
0 commit comments