💬 Commit message: Update 2026-02-06 21:50:58, 15 files, 983 lines

📁 Files changed: 15
📝 Lines changed: 983

  • .gitignore
  • browser.ts.html
  • clover.xml
  • coverage-final.json
  • index.html
  • logger.ts.html
  • server.ts.html
  • types.ts.html
  • package.json
  • browser.integration.test.ts
  • logger.test.ts
  • server.test.ts
  • vitest.all.config.ts
  • vitest.config.ts
  • vitest.integration.config.ts
This commit is contained in:
Adam Ladachowski
2026-02-06 21:50:58 +01:00
parent 880e24877d
commit 4a85e0087c
15 changed files with 635 additions and 462 deletions
+2
View File
@@ -2,3 +2,5 @@
/public/ /public/
/dist/ /dist/
/screenshots/ /screenshots/
coverage/
screenshots/
+156 -156
View File
@@ -23,30 +23,30 @@
<div class='clearfix'> <div class='clearfix'>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">58.42% </span> <span class="strong">91.01% </span>
<span class="quiet">Statements</span> <span class="quiet">Statements</span>
<span class='fraction'>52/89</span> <span class='fraction'>81/89</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">75.67% </span> <span class="strong">89.18% </span>
<span class="quiet">Branches</span> <span class="quiet">Branches</span>
<span class='fraction'>28/37</span> <span class='fraction'>33/37</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">80.95% </span> <span class="strong">85.71% </span>
<span class="quiet">Functions</span> <span class="quiet">Functions</span>
<span class='fraction'>17/21</span> <span class='fraction'>18/21</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">58.42% </span> <span class="strong">91.01% </span>
<span class="quiet">Lines</span> <span class="quiet">Lines</span>
<span class='fraction'>52/89</span> <span class='fraction'>81/89</span>
</div> </div>
@@ -61,7 +61,7 @@
</div> </div>
</template> </template>
</div> </div>
<div class='status-line medium'></div> <div class='status-line high'></div>
<pre><table class="coverage"> <pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a> <a name='L2'></a><a href='#L2'>2</a>
@@ -270,13 +270,13 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span> <span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span> <span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span> <span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span> <span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -284,43 +284,43 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">44x</span>
<span class="cline-any cline-yes">12x</span> <span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">32x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -329,8 +329,8 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
@@ -346,111 +346,67 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span> <span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-yes">14x</span> <span class="cline-any cline-yes">28x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -458,6 +414,50 @@
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -485,44 +485,44 @@ export class ClaudeBrowser {
}; };
} }
&nbsp; &nbsp;
async <span class="fstat-no" title="function not covered" >launch(): Promise&lt;void&gt; {</span> async launch(): Promise&lt;void&gt; {
<span class="cstat-no" title="statement not covered" > this.browser = await webkit.launch({ headless: this.options.headless });</span> this.browser = await webkit.launch({ headless: this.options.headless });
<span class="cstat-no" title="statement not covered" > this.context = await this.browser.newContext({</span> this.context = await this.browser.newContext({
viewport: { viewport: {
width: this.options.width, width: this.options.width,
height: this.options.height, height: this.options.height,
}, },
}); });
<span class="cstat-no" title="statement not covered" > this.page = await this.context.newPage();</span> this.page = await this.context.newPage();
} }
&nbsp; &nbsp;
async close(): Promise&lt;void&gt; { async close(): Promise&lt;void&gt; {
<span class="missing-if-branch" title="if path not taken" >I</span>if (this.browser) { if (this.browser) {
<span class="cstat-no" title="statement not covered" > await this.browser.close();</span> await this.browser.close();
<span class="cstat-no" title="statement not covered" > this.browser = null;</span> this.browser = null;
<span class="cstat-no" title="statement not covered" > this.context = null;</span> this.context = null;
<span class="cstat-no" title="statement not covered" > this.page = null;</span> this.page = null;
} }
} }
&nbsp; &nbsp;
private ensurePage(): Page { private ensurePage(): Page {
<span class="missing-if-branch" title="else path not taken" >E</span>if (!this.page) { if (!this.page) {
throw new Error('Browser not launched. Call launch() first.'); throw new Error('Browser not launched. Call launch() first.');
} }
<span class="cstat-no" title="statement not covered" > return this.page;</span> return this.page;
} }
&nbsp; &nbsp;
async goto(url: string): Promise&lt;{ url: string; title: string }&gt; { async goto(url: string): Promise&lt;{ url: string; title: string }&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
await page.goto(url, { waitUntil: 'networkidle' }); await page.goto(url, { waitUntil: 'networkidle' });
<span class="cstat-no" title="statement not covered" > return { url: page.url(), title: await page.title() };</span> return { url: page.url(), title: await page.title() };
} }
&nbsp; &nbsp;
async click(selector: string): Promise&lt;{ url: string }&gt; { async click(selector: string): Promise&lt;{ url: string }&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
await page.click(selector); await page.click(selector);
<span class="cstat-no" title="statement not covered" > await page.waitForLoadState('networkidle').catch(<span class="fstat-no" title="function not covered" >() =&gt; {</span>});</span> await page.waitForLoadState('networkidle').catch(<span class="fstat-no" title="function not covered" >() =&gt; {</span>});
<span class="cstat-no" title="statement not covered" > return { url: page.url() };</span> return { url: page.url() };
} }
&nbsp; &nbsp;
async type(selector: string, text: string): Promise&lt;void&gt; { async type(selector: string, text: string): Promise&lt;void&gt; {
@@ -551,7 +551,7 @@ export class ClaudeBrowser {
const page = this.ensurePage(); const page = this.ensurePage();
const resolvedPath = resolve(path || <span class="branch-1 cbranch-no" title="branch not covered" >'screenshot.png')</span>; const resolvedPath = resolve(path || <span class="branch-1 cbranch-no" title="branch not covered" >'screenshot.png')</span>;
const buffer = await page.screenshot({ path: resolvedPath, fullPage }); const buffer = await page.screenshot({ path: resolvedPath, fullPage });
<span class="cstat-no" title="statement not covered" > return { path: resolvedPath, buffer };</span> return { path: resolvedPath, buffer };
} }
&nbsp; &nbsp;
async getUrl(): Promise&lt;{ url: string; title: string }&gt; { async getUrl(): Promise&lt;{ url: string; title: string }&gt; {
@@ -562,25 +562,25 @@ export class ClaudeBrowser {
async getHtml(full = false): Promise&lt;string&gt; { async getHtml(full = false): Promise&lt;string&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
const html = await page.content(); const html = await page.content();
<span class="cstat-no" title="statement not covered" > return full ? html : html.slice(0, 10000);</span> return full ? html : html.slice(0, 10000);
} }
&nbsp; &nbsp;
async back(): Promise&lt;{ url: string }&gt; { async back(): Promise&lt;{ url: string }&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
await page.goBack(); await page.goBack();
<span class="cstat-no" title="statement not covered" > return { url: page.url() };</span> return { url: page.url() };
} }
&nbsp; &nbsp;
async forward(): Promise&lt;{ url: string }&gt; { async forward(): Promise&lt;{ url: string }&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
await page.goForward(); await page.goForward();
<span class="cstat-no" title="statement not covered" > return { url: page.url() };</span> return { url: page.url() };
} }
&nbsp; &nbsp;
async reload(): Promise&lt;{ url: string }&gt; { async reload(): Promise&lt;{ url: string }&gt; {
const page = this.ensurePage(); const page = this.ensurePage();
await page.reload(); await page.reload();
<span class="cstat-no" title="statement not covered" > return { url: page.url() };</span> return { url: page.url() };
} }
&nbsp; &nbsp;
async wait(ms = 1000): Promise&lt;void&gt; { async wait(ms = 1000): Promise&lt;void&gt; {
@@ -589,10 +589,10 @@ export class ClaudeBrowser {
} }
&nbsp; &nbsp;
async newPage(): Promise&lt;void&gt; { async newPage(): Promise&lt;void&gt; {
<span class="missing-if-branch" title="else path not taken" >E</span>if (!this.context) { if (!this.context) {
throw new Error('Browser not launched. Call launch() first.'); throw new Error('Browser not launched. Call launch() first.');
} }
<span class="cstat-no" title="statement not covered" > this.page = await this.context.newPage();</span> this.page = await this.context.newPage();
} }
&nbsp; &nbsp;
async eval(script: string): Promise&lt;unknown&gt; { async eval(script: string): Promise&lt;unknown&gt; {
@@ -605,11 +605,11 @@ export class ClaudeBrowser {
switch (cmd.cmd) { switch (cmd.cmd) {
case 'goto': { case 'goto': {
const result = await this.goto(cmd.url); const result = await this.goto(cmd.url);
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'click': { case 'click': {
const result = await this.click(cmd.selector); const result = await this.click(cmd.selector);
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'type': { case 'type': {
await this.type(cmd.selector, cmd.text); await this.type(cmd.selector, cmd.text);
@@ -617,39 +617,39 @@ export class ClaudeBrowser {
} }
case 'query': { case 'query': {
const elements = await this.query(cmd.selector); const elements = await this.query(cmd.selector);
<span class="cstat-no" title="statement not covered" > return { ok: true, count: elements.length, elements };</span> return { ok: true, count: elements.length, elements };
} }
case 'screenshot': { case 'screenshot': {
const result = await this.screenshot(cmd.path, cmd.fullPage); const result = await this.screenshot(cmd.path, cmd.fullPage);
<span class="cstat-no" title="statement not covered" > return { ok: true, path: result.path };</span> return { ok: true, path: result.path };
} }
case 'url': { case 'url': {
const result = await this.getUrl(); const result = await this.getUrl();
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'html': { case 'html': {
const html = await this.getHtml(cmd.full); const html = await this.getHtml(cmd.full);
<span class="cstat-no" title="statement not covered" > return { ok: true, html };</span> return { ok: true, html };
} }
case 'back': { case 'back': {
const result = await this.back(); const result = await this.back();
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'forward': { case 'forward': {
const result = await this.forward(); const result = await this.forward();
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'reload': { case 'reload': {
const result = await this.reload(); const result = await this.reload();
<span class="cstat-no" title="statement not covered" > return { ok: true, ...result };</span> return { ok: true, ...result };
} }
case 'wait': { case 'wait': {
await this.wait(cmd.ms); await this.wait(cmd.ms);
<span class="cstat-no" title="statement not covered" > return { ok: true };</span> return { ok: true };
} }
case 'newpage': { case 'newpage': {
await this.newPage(); await this.newPage();
<span class="cstat-no" title="statement not covered" > return { ok: true };</span> return { ok: true };
} }
case 'close': { case 'close': {
await this.close(); await this.close();
@@ -657,7 +657,7 @@ export class ClaudeBrowser {
} }
case 'eval': { case 'eval': {
const result = await this.eval(cmd.script); const result = await this.eval(cmd.script);
<span class="cstat-no" title="statement not covered" > return { ok: true, result };</span> return { ok: true, result };
} }
<span class="branch-14 cbranch-no" title="branch not covered" > default: {</span> <span class="branch-14 cbranch-no" title="branch not covered" > default: {</span>
const _exhaustive: never = <span class="cstat-no" title="statement not covered" >cmd;</span> const _exhaustive: never = <span class="cstat-no" title="statement not covered" >cmd;</span>
@@ -676,7 +676,7 @@ export class ClaudeBrowser {
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-02-06T20:43:25.074Z at 2026-02-06T20:50:54.063Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>
-195
View File
@@ -1,195 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1770410605079" clover="3.2.0">
<project timestamp="1770410605079" name="All files">
<metrics statements="177" coveredstatements="108" conditionals="89" coveredconditionals="63" methods="50" coveredmethods="38" elements="316" coveredelements="209" complexity="0" loc="177" ncloc="177" packages="1" files="4" classes="4"/>
<file name="browser.ts" path="/Users/chi/Projects/claude-browse/src/browser.ts">
<metrics statements="89" coveredstatements="52" conditionals="37" coveredconditionals="28" methods="21" coveredmethods="17"/>
<line num="6" count="6" type="stmt"/>
<line num="7" count="6" type="stmt"/>
<line num="8" count="6" type="stmt"/>
<line num="12" count="6" type="stmt"/>
<line num="20" count="0" type="stmt"/>
<line num="21" count="0" type="stmt"/>
<line num="27" count="0" type="stmt"/>
<line num="31" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="32" count="0" type="stmt"/>
<line num="33" count="0" type="stmt"/>
<line num="34" count="0" type="stmt"/>
<line num="35" count="0" type="stmt"/>
<line num="40" count="12" type="cond" truecount="1" falsecount="1"/>
<line num="41" count="12" type="stmt"/>
<line num="43" count="0" type="stmt"/>
<line num="47" count="1" type="stmt"/>
<line num="48" count="1" type="stmt"/>
<line num="49" count="0" type="stmt"/>
<line num="53" count="1" type="stmt"/>
<line num="54" count="1" type="stmt"/>
<line num="55" count="0" type="stmt"/>
<line num="56" count="0" type="stmt"/>
<line num="60" count="1" type="stmt"/>
<line num="61" count="1" type="stmt"/>
<line num="65" count="1" type="stmt"/>
<line num="66" count="1" type="stmt"/>
<line num="67" count="0" type="stmt"/>
<line num="68" count="0" type="stmt"/>
<line num="69" count="0" type="stmt"/>
<line num="70" count="0" type="stmt"/>
<line num="72" count="0" type="stmt"/>
<line num="82" count="1" type="stmt"/>
<line num="83" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="84" count="1" type="stmt"/>
<line num="85" count="0" type="stmt"/>
<line num="89" count="1" type="stmt"/>
<line num="90" count="1" type="stmt"/>
<line num="94" count="1" type="stmt"/>
<line num="95" count="1" type="stmt"/>
<line num="96" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="100" count="1" type="stmt"/>
<line num="101" count="1" type="stmt"/>
<line num="102" count="0" type="stmt"/>
<line num="106" count="1" type="stmt"/>
<line num="107" count="1" type="stmt"/>
<line num="108" count="0" type="stmt"/>
<line num="112" count="1" type="stmt"/>
<line num="113" count="1" type="stmt"/>
<line num="114" count="0" type="stmt"/>
<line num="118" count="1" type="stmt"/>
<line num="119" count="1" type="stmt"/>
<line num="123" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="124" count="1" type="stmt"/>
<line num="126" count="0" type="stmt"/>
<line num="130" count="1" type="stmt"/>
<line num="131" count="1" type="stmt"/>
<line num="135" count="14" type="stmt"/>
<line num="136" count="14" type="cond" truecount="14" falsecount="1"/>
<line num="138" count="1" type="stmt"/>
<line num="139" count="0" type="stmt"/>
<line num="142" count="1" type="stmt"/>
<line num="143" count="0" type="stmt"/>
<line num="146" count="1" type="stmt"/>
<line num="147" count="0" type="stmt"/>
<line num="150" count="1" type="stmt"/>
<line num="151" count="0" type="stmt"/>
<line num="154" count="1" type="stmt"/>
<line num="155" count="0" type="stmt"/>
<line num="158" count="1" type="stmt"/>
<line num="159" count="0" type="stmt"/>
<line num="162" count="1" type="stmt"/>
<line num="163" count="0" type="stmt"/>
<line num="166" count="1" type="stmt"/>
<line num="167" count="0" type="stmt"/>
<line num="170" count="1" type="stmt"/>
<line num="171" count="0" type="stmt"/>
<line num="174" count="1" type="stmt"/>
<line num="175" count="0" type="stmt"/>
<line num="178" count="1" type="stmt"/>
<line num="179" count="0" type="stmt"/>
<line num="182" count="1" type="stmt"/>
<line num="183" count="0" type="stmt"/>
<line num="186" count="1" type="stmt"/>
<line num="187" count="1" type="stmt"/>
<line num="190" count="1" type="stmt"/>
<line num="191" count="0" type="stmt"/>
<line num="194" count="0" type="stmt"/>
<line num="195" count="0" type="stmt"/>
<line num="199" count="13" type="stmt"/>
</file>
<file name="logger.ts" path="/Users/chi/Projects/claude-browse/src/logger.ts">
<metrics statements="37" coveredstatements="34" conditionals="42" coveredconditionals="28" methods="16" coveredmethods="13"/>
<line num="5" count="2" type="stmt"/>
<line num="23" count="2" type="stmt"/>
<line num="41" count="23" type="stmt"/>
<line num="45" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="60" count="18" type="cond" truecount="8" falsecount="1"/>
<line num="62" count="4" type="stmt"/>
<line num="65" count="5" type="stmt"/>
<line num="67" count="2" type="stmt"/>
<line num="69" count="2" type="cond" truecount="2" falsecount="0"/>
<line num="71" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="73" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="75" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="77" count="3" type="stmt"/>
<line num="82" count="11" type="cond" truecount="1" falsecount="1"/>
<line num="83" count="11" type="cond" truecount="1" falsecount="1"/>
<line num="84" count="11" type="stmt"/>
<line num="85" count="11" type="cond" truecount="2" falsecount="0"/>
<line num="86" count="11" type="stmt"/>
<line num="100" count="2" type="stmt"/>
<line num="101" count="3" type="stmt"/>
<line num="102" count="2" type="cond" truecount="1" falsecount="1"/>
<line num="103" count="2" type="cond" truecount="1" falsecount="1"/>
<line num="104" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="105" count="2" type="stmt"/>
<line num="106" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="107" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="111" count="12" type="cond" truecount="2" falsecount="0"/>
<line num="112" count="1" type="stmt"/>
<line num="114" count="11" type="stmt"/>
<line num="115" count="11" type="cond" truecount="2" falsecount="0"/>
<line num="116" count="12" type="cond" truecount="2" falsecount="0"/>
<line num="117" count="12" type="stmt"/>
<line num="123" count="6" type="stmt"/>
<line num="125" count="9" type="stmt"/>
<line num="128" count="9" type="stmt"/>
<line num="134" count="2" type="stmt"/>
<line num="137" count="2" type="stmt"/>
</file>
<file name="server.ts" path="/Users/chi/Projects/claude-browse/src/server.ts">
<metrics statements="51" coveredstatements="22" conditionals="10" coveredconditionals="7" methods="13" coveredmethods="8"/>
<line num="13" count="0" type="stmt"/>
<line num="14" count="0" type="stmt"/>
<line num="15" count="0" type="stmt"/>
<line num="16" count="0" type="stmt"/>
<line num="17" count="0" type="stmt"/>
<line num="18" count="0" type="stmt"/>
<line num="19" count="0" type="stmt"/>
<line num="22" count="0" type="stmt"/>
<line num="25" count="0" type="stmt"/>
<line num="26" count="0" type="stmt"/>
<line num="27" count="0" type="stmt"/>
<line num="30" count="0" type="stmt"/>
<line num="35" count="3" type="stmt"/>
<line num="36" count="3" type="stmt"/>
<line num="40" count="3" type="stmt"/>
<line num="41" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="42" count="3" type="stmt"/>
<line num="43" count="3" type="stmt"/>
<line num="47" count="3" type="stmt"/>
<line num="48" count="3" type="stmt"/>
<line num="52" count="7" type="stmt"/>
<line num="56" count="7" type="stmt"/>
<line num="57" count="7" type="cond" truecount="2" falsecount="0"/>
<line num="58" count="7" type="stmt"/>
<line num="60" count="7" type="cond" truecount="1" falsecount="1"/>
<line num="61" count="0" type="stmt"/>
<line num="62" count="0" type="stmt"/>
<line num="63" count="0" type="stmt"/>
<line num="64" count="0" type="stmt"/>
<line num="67" count="7" type="stmt"/>
<line num="68" count="7" type="stmt"/>
<line num="69" count="7" type="stmt"/>
<line num="71" count="0" type="stmt"/>
<line num="72" count="0" type="stmt"/>
<line num="73" count="0" type="stmt"/>
<line num="78" count="0" type="stmt"/>
<line num="80" count="0" type="stmt"/>
<line num="81" count="0" type="stmt"/>
<line num="82" count="0" type="stmt"/>
<line num="83" count="0" type="stmt"/>
<line num="89" count="1" type="stmt"/>
<line num="90" count="1" type="cond" truecount="1" falsecount="1"/>
<line num="91" count="0" type="stmt"/>
<line num="92" count="0" type="stmt"/>
<line num="94" count="1" type="stmt"/>
<line num="95" count="1" type="stmt"/>
<line num="99" count="2" type="stmt"/>
<line num="103" count="7" type="stmt"/>
<line num="108" count="0" type="stmt"/>
<line num="109" count="0" type="stmt"/>
<line num="110" count="0" type="stmt"/>
</file>
<file name="types.ts" path="/Users/chi/Projects/claude-browse/src/types.ts">
<metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
</file>
</project>
</coverage>
File diff suppressed because one or more lines are too long
+38 -38
View File
@@ -23,30 +23,30 @@
<div class='clearfix'> <div class='clearfix'>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">60.89% </span> <span class="strong">80.44% </span>
<span class="quiet">Statements</span> <span class="quiet">Statements</span>
<span class='fraction'>109/179</span> <span class='fraction'>144/179</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">70.78% </span> <span class="strong">85.39% </span>
<span class="quiet">Branches</span> <span class="quiet">Branches</span>
<span class='fraction'>63/89</span> <span class='fraction'>76/89</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">76% </span> <span class="strong">82% </span>
<span class="quiet">Functions</span> <span class="quiet">Functions</span>
<span class='fraction'>38/50</span> <span class='fraction'>41/50</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">61.01% </span> <span class="strong">80.79% </span>
<span class="quiet">Lines</span> <span class="quiet">Lines</span>
<span class='fraction'>108/177</span> <span class='fraction'>143/177</span>
</div> </div>
@@ -61,7 +61,7 @@
</div> </div>
</template> </template>
</div> </div>
<div class='status-line medium'></div> <div class='status-line high'></div>
<div class="pad1"> <div class="pad1">
<table class="coverage-summary"> <table class="coverage-summary">
<thead> <thead>
@@ -79,48 +79,48 @@
</tr> </tr>
</thead> </thead>
<tbody><tr> <tbody><tr>
<td class="file medium" data-value="browser.ts"><a href="browser.ts.html">browser.ts</a></td> <td class="file high" data-value="browser.ts"><a href="browser.ts.html">browser.ts</a></td>
<td data-value="58.42" class="pic medium"> <td data-value="91.01" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 58%"></div><div class="cover-empty" style="width: 42%"></div></div> <div class="chart"><div class="cover-fill" style="width: 91%"></div><div class="cover-empty" style="width: 9%"></div></div>
</td> </td>
<td data-value="58.42" class="pct medium">58.42%</td> <td data-value="91.01" class="pct high">91.01%</td>
<td data-value="89" class="abs medium">52/89</td> <td data-value="89" class="abs high">81/89</td>
<td data-value="75.67" class="pct medium">75.67%</td> <td data-value="89.18" class="pct high">89.18%</td>
<td data-value="37" class="abs medium">28/37</td> <td data-value="37" class="abs high">33/37</td>
<td data-value="80.95" class="pct high">80.95%</td> <td data-value="85.71" class="pct high">85.71%</td>
<td data-value="21" class="abs high">17/21</td> <td data-value="21" class="abs high">18/21</td>
<td data-value="58.42" class="pct medium">58.42%</td> <td data-value="91.01" class="pct high">91.01%</td>
<td data-value="89" class="abs medium">52/89</td> <td data-value="89" class="abs high">81/89</td>
</tr> </tr>
<tr> <tr>
<td class="file high" data-value="logger.ts"><a href="logger.ts.html">logger.ts</a></td> <td class="file high" data-value="logger.ts"><a href="logger.ts.html">logger.ts</a></td>
<td data-value="89.47" class="pic high"> <td data-value="97.36" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 89%"></div><div class="cover-empty" style="width: 11%"></div></div> <div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td> </td>
<td data-value="89.47" class="pct high">89.47%</td> <td data-value="97.36" class="pct high">97.36%</td>
<td data-value="38" class="abs high">34/38</td> <td data-value="38" class="abs high">37/38</td>
<td data-value="66.66" class="pct medium">66.66%</td> <td data-value="85.71" class="pct high">85.71%</td>
<td data-value="42" class="abs medium">28/42</td> <td data-value="42" class="abs high">36/42</td>
<td data-value="81.25" class="pct high">81.25%</td> <td data-value="93.75" class="pct high">93.75%</td>
<td data-value="16" class="abs high">13/16</td> <td data-value="16" class="abs high">15/16</td>
<td data-value="91.89" class="pct high">91.89%</td> <td data-value="100" class="pct high">100%</td>
<td data-value="37" class="abs high">34/37</td> <td data-value="37" class="abs high">37/37</td>
</tr> </tr>
<tr> <tr>
<td class="file low" data-value="server.ts"><a href="server.ts.html">server.ts</a></td> <td class="file medium" data-value="server.ts"><a href="server.ts.html">server.ts</a></td>
<td data-value="44.23" class="pic low"> <td data-value="50" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 44%"></div><div class="cover-empty" style="width: 56%"></div></div> <div class="chart"><div class="cover-fill" style="width: 50%"></div><div class="cover-empty" style="width: 50%"></div></div>
</td> </td>
<td data-value="44.23" class="pct low">44.23%</td> <td data-value="50" class="pct medium">50%</td>
<td data-value="52" class="abs low">23/52</td> <td data-value="52" class="abs medium">26/52</td>
<td data-value="70" class="pct medium">70%</td> <td data-value="70" class="pct medium">70%</td>
<td data-value="10" class="abs medium">7/10</td> <td data-value="10" class="abs medium">7/10</td>
<td data-value="61.53" class="pct medium">61.53%</td> <td data-value="61.53" class="pct medium">61.53%</td>
<td data-value="13" class="abs medium">8/13</td> <td data-value="13" class="abs medium">8/13</td>
<td data-value="43.13" class="pct low">43.13%</td> <td data-value="49.01" class="pct low">49.01%</td>
<td data-value="51" class="abs low">22/51</td> <td data-value="51" class="abs low">25/51</td>
</tr> </tr>
<tr> <tr>
@@ -146,7 +146,7 @@
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-02-06T20:43:25.074Z at 2026-02-06T20:50:54.063Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>
+44 -44
View File
@@ -23,30 +23,30 @@
<div class='clearfix'> <div class='clearfix'>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">89.47% </span> <span class="strong">97.36% </span>
<span class="quiet">Statements</span> <span class="quiet">Statements</span>
<span class='fraction'>34/38</span> <span class='fraction'>37/38</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">66.66% </span> <span class="strong">85.71% </span>
<span class="quiet">Branches</span> <span class="quiet">Branches</span>
<span class='fraction'>28/42</span> <span class='fraction'>36/42</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">81.25% </span> <span class="strong">93.75% </span>
<span class="quiet">Functions</span> <span class="quiet">Functions</span>
<span class='fraction'>13/16</span> <span class='fraction'>15/16</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">91.89% </span> <span class="strong">100% </span>
<span class="quiet">Lines</span> <span class="quiet">Lines</span>
<span class='fraction'>34/37</span> <span class='fraction'>37/37</span>
</div> </div>
@@ -240,11 +240,11 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">23x</span> <span class="cline-any cline-yes">45x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span> <span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -259,33 +259,33 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">18x</span> <span class="cline-any cline-yes">32x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span> <span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span> <span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -301,22 +301,22 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">3x</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">12x</span> <span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">11x</span> <span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">12x</span> <span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">12x</span> <span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -324,10 +324,10 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">6x</span> <span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span> <span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">9x</span> <span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -407,19 +407,19 @@ export function getCommandDetail(cmd: CommandLike): string | undefined {
case 'screenshot': case 'screenshot':
return chalk.dim(cmd.path || 'screenshot.png'); return chalk.dim(cmd.path || 'screenshot.png');
case 'html': case 'html':
return cmd.full ? <span class="branch-0 cbranch-no" title="branch not covered" >chalk.dim('(full)') : u</span>ndefined; return cmd.full ? chalk.dim('(full)') : undefined;
case 'wait': case 'wait':
return chalk.dim(`${cmd.ms || <span class="branch-1 cbranch-no" title="branch not covered" >1000}</span>ms`); return chalk.dim(`${cmd.ms || 1000}ms`);
<span class="branch-7 cbranch-no" title="branch not covered" > case 'eval':</span> case 'eval':
<span class="cstat-no" title="statement not covered" > return chalk.dim(truncate(cmd.script || '', 50));</span> return chalk.dim(truncate(cmd.script || <span class="branch-1 cbranch-no" title="branch not covered" >'', 5</span>0));
default: default:
return undefined; return undefined;
} }
} }
&nbsp; &nbsp;
export function formatCommand(cmd: CommandLike): string { export function formatCommand(cmd: CommandLike): string {
const color = cmdColor[cmd.cmd] || <span class="branch-1 cbranch-no" title="branch not covered" >chalk.white;</span> const color = cmdColor[cmd.cmd] || chalk.white;
const icon = icons[cmd.cmd] || <span class="branch-1 cbranch-no" title="branch not covered" >'•';</span> const icon = icons[cmd.cmd] || '•';
const detail = getCommandDetail(cmd); const detail = getCommandDetail(cmd);
const suffix = detail ? ` ${detail}` : ''; const suffix = detail ? ` ${detail}` : '';
return `${ts()} ${chalk.bold(color(icon))} ${color(cmd.cmd.toUpperCase())}${suffix}`; return `${ts()} ${chalk.bold(color(icon))} ${color(cmd.cmd.toUpperCase())}${suffix}`;
@@ -440,10 +440,10 @@ const resultFormatters: Record&lt;string, (r: ResultLike) =&gt; string | undefin
goto: (r) =&gt; r.title, goto: (r) =&gt; r.title,
click: (r) =&gt; (r.url ? `→ ${r.url}` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span> click: (r) =&gt; (r.url ? `→ ${r.url}` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span>
query: (r) =&gt; (r.count !== undefined ? `Found ${r.count} element(s)` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span> query: (r) =&gt; (r.count !== undefined ? `Found ${r.count} element(s)` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span>
screenshot: <span class="fstat-no" title="function not covered" >(r</span>) =&gt; (<span class="cstat-no" title="statement not covered" >r.path ? `Saved to ${r.path}` : undefined),</span> screenshot: (r) =&gt; (r.path ? `Saved to ${r.path}` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span>
url: (r) =&gt; r.url, url: (r) =&gt; r.url,
html: (r) =&gt; (r.html !== undefined ? `${r.html.length} chars` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span> html: (r) =&gt; (r.html !== undefined ? `${r.html.length} chars` : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span>
eval: <span class="fstat-no" title="function not covered" >(r</span>) =&gt; (<span class="cstat-no" title="statement not covered" >r.result !== undefined ? truncate(JSON.stringify(r.result), 80) : undefined),</span> eval: (r) =&gt; (r.result !== undefined ? truncate(JSON.stringify(r.result), 80) : <span class="branch-1 cbranch-no" title="branch not covered" >undefined),</span>
}; };
&nbsp; &nbsp;
export function formatResult(cmd: CommandLike, result: ResultLike): string { export function formatResult(cmd: CommandLike, result: ResultLike): string {
@@ -481,7 +481,7 @@ export const stderrLogger = createLogger(<span class="fstat-no" title="function
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-02-06T20:43:25.074Z at 2026-02-06T20:50:54.063Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>
+21 -21
View File
@@ -23,9 +23,9 @@
<div class='clearfix'> <div class='clearfix'>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">44.23% </span> <span class="strong">50% </span>
<span class="quiet">Statements</span> <span class="quiet">Statements</span>
<span class='fraction'>23/52</span> <span class='fraction'>26/52</span>
</div> </div>
@@ -44,9 +44,9 @@
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">43.13% </span> <span class="strong">49.01% </span>
<span class="quiet">Lines</span> <span class="quiet">Lines</span>
<span class='fraction'>22/51</span> <span class='fraction'>25/51</span>
</div> </div>
@@ -61,7 +61,7 @@
</div> </div>
</template> </template>
</div> </div>
<div class='status-line low'></div> <div class='status-line medium'></div>
<pre><table class="coverage"> <pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a> <a name='L2'></a><a href='#L2'>2</a>
@@ -225,28 +225,28 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span> <span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -276,7 +276,7 @@
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-yes">15x</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span> <span class="cline-any cline-neutral">&nbsp;</span>
@@ -355,9 +355,9 @@ export class BrowserServer {
logger.result(cmd, result); logger.result(cmd, result);
res.json(result); res.json(result);
} catch (err) { } catch (err) {
const error = (<span class="cstat-no" title="statement not covered" >err as Error).message;</span> const error = (err as Error).message;
<span class="cstat-no" title="statement not covered" > console.log(`${ts()} ${logSymbols.error} ${chalk.red(error)}`);</span> console.log(`${ts()} ${logSymbols.error} ${chalk.red(error)}`);
<span class="cstat-no" title="statement not covered" > res.status(500).json({ ok: false, error });</span> res.status(500).json({ ok: false, error });
} }
} }
&nbsp; &nbsp;
@@ -403,7 +403,7 @@ export async function <span class="fstat-no" title="function not covered" >start
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-02-06T20:43:25.074Z at 2026-02-06T20:50:54.063Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>
+1 -1
View File
@@ -409,7 +409,7 @@ export type CommandResponse = SuccessResponse | ErrorResponse;
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-02-06T20:43:25.074Z at 2026-02-06T20:50:54.063Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>
+4 -1
View File
@@ -21,8 +21,11 @@
"fix": "biome check --write src/", "fix": "biome check --write src/",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test:unit": "vitest run --coverage --passWithNoTests", "test:unit": "vitest run --coverage --passWithNoTests",
"test:integration": "vitest run --coverage --config vitest.integration.config.ts",
"test:watch": "vitest", "test:watch": "vitest",
"test": "npm run fix && npm run typecheck && npm run test:unit && npm run build", "test:all": "vitest run --coverage --config vitest.all.config.ts",
"test": "npm run fix && npm run typecheck && npm run test:all && npm run build",
"coverage": "npm run test:all && open coverage/index.html",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"keywords": [ "keywords": [
+203
View File
@@ -0,0 +1,203 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { ClaudeBrowser } from './browser.js';
describe('ClaudeBrowser Integration', () => {
let browser: ClaudeBrowser;
beforeAll(async () => {
browser = new ClaudeBrowser({ headless: true });
await browser.launch();
}, 30000);
afterAll(async () => {
await browser.close();
});
describe('navigation', () => {
it('navigates to a URL and returns title', async () => {
const result = await browser.goto('https://example.com');
expect(result.url).toContain('example.com');
expect(result.title).toBe('Example Domain');
});
it('gets current URL and title', async () => {
const result = await browser.getUrl();
expect(result.url).toContain('example.com');
expect(result.title).toBe('Example Domain');
});
it('gets page HTML', async () => {
const html = await browser.getHtml();
expect(html.toLowerCase()).toContain('<!doctype html>');
expect(html).toContain('Example Domain');
});
it('gets full page HTML', async () => {
const html = await browser.getHtml(true);
expect(html.toLowerCase()).toContain('<!doctype html>');
expect(html.length).toBeGreaterThan(100);
});
it('reloads the page', async () => {
const result = await browser.reload();
expect(result.url).toContain('example.com');
});
});
describe('DOM interaction', () => {
it('queries elements', async () => {
const elements = await browser.query('h1');
expect(elements.length).toBe(1);
expect(elements[0].tag).toBe('h1');
expect(elements[0].text).toContain('Example Domain');
});
it('queries multiple elements', async () => {
const elements = await browser.query('p');
expect(elements.length).toBeGreaterThan(0);
expect(elements[0].tag).toBe('p');
});
it('clicks an element', async () => {
await browser.goto('https://example.com');
const result = await browser.click('a');
expect(result.url).toBeDefined();
});
});
describe('screenshots', () => {
it('takes a screenshot', async () => {
await browser.goto('https://example.com');
const result = await browser.screenshot('screenshots/test-integration.png');
expect(result.path).toContain('test-integration.png');
expect(result.buffer).toBeDefined();
});
it('takes a full page screenshot', async () => {
const result = await browser.screenshot('screenshots/test-full.png', true);
expect(result.path).toContain('test-full.png');
});
});
describe('wait', () => {
it('waits for specified time', async () => {
const start = Date.now();
await browser.wait(100);
const elapsed = Date.now() - start;
expect(elapsed).toBeGreaterThanOrEqual(90);
});
});
describe('eval', () => {
it('evaluates JavaScript', async () => {
await browser.goto('https://example.com');
const result = await browser.eval('document.title');
expect(result).toBe('Example Domain');
});
it('evaluates expressions', async () => {
const result = await browser.eval('1 + 1');
expect(result).toBe(2);
});
it('evaluates complex expressions', async () => {
const result = await browser.eval('document.querySelectorAll("p").length');
expect(result).toBeGreaterThan(0);
});
});
describe('pages', () => {
it('creates a new page', async () => {
await browser.newPage();
const result = await browser.getUrl();
expect(result.url).toBe('about:blank');
});
it('navigates in new page', async () => {
const result = await browser.goto('https://example.com');
expect(result.title).toBe('Example Domain');
});
});
describe('executeCommand', () => {
it('handles goto command', async () => {
const result = await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.title).toBe('Example Domain');
}
});
it('handles query command', async () => {
const result = await browser.executeCommand({ cmd: 'query', selector: 'h1' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.count).toBe(1);
}
});
it('handles url command', async () => {
const result = await browser.executeCommand({ cmd: 'url' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.url).toContain('example.com');
}
});
it('handles html command', async () => {
const result = await browser.executeCommand({ cmd: 'html' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.html).toContain('Example');
}
});
it('handles wait command', async () => {
const result = await browser.executeCommand({ cmd: 'wait', ms: 50 });
expect(result.ok).toBe(true);
});
it('handles eval command', async () => {
const result = await browser.executeCommand({ cmd: 'eval', script: '2+2' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.result).toBe(4);
}
});
it('handles screenshot command', async () => {
const result = await browser.executeCommand({
cmd: 'screenshot',
path: 'screenshots/cmd-test.png',
});
expect(result.ok).toBe(true);
});
it('handles reload command', async () => {
const result = await browser.executeCommand({ cmd: 'reload' });
expect(result.ok).toBe(true);
});
it('handles click command', async () => {
await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
const result = await browser.executeCommand({ cmd: 'click', selector: 'a' });
expect(result.ok).toBe(true);
});
it('handles newpage command', async () => {
const result = await browser.executeCommand({ cmd: 'newpage' });
expect(result.ok).toBe(true);
});
it('handles back command', async () => {
await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
const result = await browser.executeCommand({ cmd: 'back' });
expect(result.ok).toBe(true);
});
it('handles forward command', async () => {
const result = await browser.executeCommand({ cmd: 'forward' });
expect(result.ok).toBe(true);
});
});
});
+61
View File
@@ -66,6 +66,36 @@ describe('getCommandDetail', () => {
const result = getCommandDetail({ cmd: 'wait', ms: 500 }); const result = getCommandDetail({ cmd: 'wait', ms: 500 });
expect(result).toContain('500'); expect(result).toContain('500');
}); });
it('returns default ms for wait command without ms', () => {
const result = getCommandDetail({ cmd: 'wait' });
expect(result).toContain('1000');
});
it('returns script for eval command', () => {
const result = getCommandDetail({ cmd: 'eval', script: 'document.title' });
expect(result).toContain('document.title');
});
it('truncates long eval scripts', () => {
const longScript = 'a'.repeat(100);
const result = getCommandDetail({ cmd: 'eval', script: longScript });
expect(result).toContain('...');
});
it('returns (full) for html command with full=true', () => {
const result = getCommandDetail({ cmd: 'html', full: true });
expect(result).toContain('full');
});
it('returns undefined for html command with full=false', () => {
expect(getCommandDetail({ cmd: 'html', full: false })).toBeUndefined();
});
it('returns selector for query command', () => {
const result = getCommandDetail({ cmd: 'query', selector: '.items' });
expect(result).toContain('.items');
});
}); });
describe('formatCommand', () => { describe('formatCommand', () => {
@@ -97,6 +127,37 @@ describe('formatResult', () => {
const result = formatResult({ cmd: 'query' }, { ok: true, count: 5 }); const result = formatResult({ cmd: 'query' }, { ok: true, count: 5 });
expect(result).toContain('5'); expect(result).toContain('5');
}); });
it('formats click result with url', () => {
const result = formatResult({ cmd: 'click' }, { ok: true, url: '/page' });
expect(result).toContain('/page');
});
it('formats screenshot result with path', () => {
const result = formatResult({ cmd: 'screenshot' }, { ok: true, path: 'test.png' });
expect(result).toContain('Saved to test.png');
});
it('formats url result', () => {
const result = formatResult({ cmd: 'url' }, { ok: true, url: 'https://example.com' });
expect(result).toContain('https://example.com');
});
it('formats html result with length', () => {
const result = formatResult({ cmd: 'html' }, { ok: true, html: '<html></html>' });
expect(result).toContain('13 chars');
});
it('formats eval result', () => {
const result = formatResult({ cmd: 'eval' }, { ok: true, result: { foo: 'bar' } });
expect(result).toContain('foo');
expect(result).toContain('bar');
});
it('formats result without formatter', () => {
const result = formatResult({ cmd: 'wait' }, { ok: true });
expect(result).toBeDefined();
});
}); });
describe('createLogger', () => { describe('createLogger', () => {
+69 -1
View File
@@ -7,7 +7,7 @@ vi.mock('./browser.js', () => ({
ClaudeBrowser: class MockClaudeBrowser { ClaudeBrowser: class MockClaudeBrowser {
async launch() {} async launch() {}
async close() {} async close() {}
async executeCommand(cmd: { cmd: string; url?: string }) { async executeCommand(cmd: { cmd: string; url?: string; path?: string }) {
switch (cmd.cmd) { switch (cmd.cmd) {
case 'goto': case 'goto':
return { ok: true, url: cmd.url, title: 'Test Page' }; return { ok: true, url: cmd.url, title: 'Test Page' };
@@ -21,6 +21,22 @@ vi.mock('./browser.js', () => ({
return { ok: true, url: 'https://example.com', title: 'Example' }; return { ok: true, url: 'https://example.com', title: 'Example' };
case 'html': case 'html':
return { ok: true, html: '<html></html>' }; return { ok: true, html: '<html></html>' };
case 'back':
return { ok: true, url: '/previous' };
case 'forward':
return { ok: true, url: '/next' };
case 'reload':
return { ok: true, url: '/current' };
case 'wait':
return { ok: true };
case 'screenshot':
return { ok: true, path: cmd.path || 'screenshot.png' };
case 'eval':
return { ok: true, result: 2 };
case 'newpage':
return { ok: true };
case 'error':
throw new Error('Test error');
default: default:
return { ok: true }; return { ok: true };
} }
@@ -119,5 +135,57 @@ describe('BrowserServer', () => {
expect(res.body.ok).toBe(true); expect(res.body.ok).toBe(true);
}); });
it('handles back command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'back' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles forward command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'forward' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles reload command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'reload' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles wait command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'wait', ms: 100 }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles screenshot command', async () => {
const app = server.getApp();
const res = await request(app)
.post('/')
.send({ cmd: 'screenshot', path: 'test.png' })
.expect(200);
expect(res.body.ok).toBe(true);
});
it('handles eval command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'eval', script: '1+1' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles newpage command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'newpage' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles errors and returns 500', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'error' }).expect(500);
expect(res.body.ok).toBe(false);
expect(res.body.error).toBe('Test error');
});
}); });
}); });
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/cli.ts', 'src/mcp.ts', 'src/index.ts'],
reporter: ['text', 'html'],
reportsDirectory: './coverage',
},
testTimeout: 60000,
hookTimeout: 60000,
},
});
+4
View File
@@ -3,10 +3,14 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({ export default defineConfig({
test: { test: {
include: ['src/**/*.test.ts'], include: ['src/**/*.test.ts'],
exclude: ['src/**/*.integration.test.ts'],
coverage: { coverage: {
provider: 'v8', provider: 'v8',
include: ['src/**/*.ts'], include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/cli.ts', 'src/mcp.ts', 'src/index.ts'], exclude: ['src/**/*.test.ts', 'src/cli.ts', 'src/mcp.ts', 'src/index.ts'],
reporter: ['text', 'html'],
reportsDirectory: './coverage',
}, },
testTimeout: 30000,
}, },
}); });
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/**/*.integration.test.ts'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/cli.ts', 'src/mcp.ts', 'src/index.ts'],
reporter: ['text', 'html'],
reportsDirectory: './coverage',
},
testTimeout: 60000,
hookTimeout: 60000,
},
});