ai-functions 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-test.log +105 -0
- package/README.md +190 -86
- package/TODO.md +138 -0
- package/dist/ai-promise.d.ts +219 -0
- package/dist/ai-promise.d.ts.map +1 -0
- package/dist/ai-promise.js +610 -0
- package/dist/ai-promise.js.map +1 -0
- package/dist/ai.d.ts +285 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +842 -0
- package/dist/ai.js.map +1 -0
- package/dist/batch/anthropic.d.ts +23 -0
- package/dist/batch/anthropic.d.ts.map +1 -0
- package/dist/batch/anthropic.js +257 -0
- package/dist/batch/anthropic.js.map +1 -0
- package/dist/batch/bedrock.d.ts +64 -0
- package/dist/batch/bedrock.d.ts.map +1 -0
- package/dist/batch/bedrock.js +586 -0
- package/dist/batch/bedrock.js.map +1 -0
- package/dist/batch/cloudflare.d.ts +37 -0
- package/dist/batch/cloudflare.d.ts.map +1 -0
- package/dist/batch/cloudflare.js +289 -0
- package/dist/batch/cloudflare.js.map +1 -0
- package/dist/batch/google.d.ts +41 -0
- package/dist/batch/google.d.ts.map +1 -0
- package/dist/batch/google.js +360 -0
- package/dist/batch/google.js.map +1 -0
- package/dist/batch/index.d.ts +31 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +31 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/memory.d.ts +44 -0
- package/dist/batch/memory.d.ts.map +1 -0
- package/dist/batch/memory.js +188 -0
- package/dist/batch/memory.js.map +1 -0
- package/dist/batch/openai.d.ts +37 -0
- package/dist/batch/openai.d.ts.map +1 -0
- package/dist/batch/openai.js +403 -0
- package/dist/batch/openai.js.map +1 -0
- package/dist/batch-map.d.ts +125 -0
- package/dist/batch-map.d.ts.map +1 -0
- package/dist/batch-map.js +406 -0
- package/dist/batch-map.js.map +1 -0
- package/dist/batch-queue.d.ts +273 -0
- package/dist/batch-queue.d.ts.map +1 -0
- package/dist/batch-queue.js +271 -0
- package/dist/batch-queue.js.map +1 -0
- package/dist/context.d.ts +133 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +267 -0
- package/dist/context.js.map +1 -0
- package/dist/embeddings.d.ts +123 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +170 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/eval/index.d.ts +8 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +8 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/models.d.ts +66 -0
- package/dist/eval/models.d.ts.map +1 -0
- package/dist/eval/models.js +120 -0
- package/dist/eval/models.js.map +1 -0
- package/dist/eval/runner.d.ts +64 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +148 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/generate.d.ts +168 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +174 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +292 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +471 -0
- package/dist/primitives.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +9 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +9 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schema.d.ts +54 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +73 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +129 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +474 -106
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -8
- package/dist/types.js.map +1 -1
- package/evalite.config.ts +19 -0
- package/evals/README.md +212 -0
- package/evals/classification.eval.ts +108 -0
- package/evals/marketing.eval.ts +370 -0
- package/evals/math.eval.ts +94 -0
- package/evals/run-evals.ts +166 -0
- package/evals/structured-output.eval.ts +143 -0
- package/evals/writing.eval.ts +117 -0
- package/examples/batch-blog-posts.ts +160 -0
- package/package.json +57 -57
- package/src/ai-promise.ts +784 -0
- package/src/ai.ts +1183 -0
- package/src/batch/anthropic.ts +375 -0
- package/src/batch/bedrock.ts +801 -0
- package/src/batch/cloudflare.ts +421 -0
- package/src/batch/google.ts +491 -0
- package/src/batch/index.ts +31 -0
- package/src/batch/memory.ts +253 -0
- package/src/batch/openai.ts +557 -0
- package/src/batch-map.ts +534 -0
- package/src/batch-queue.ts +493 -0
- package/src/context.ts +332 -0
- package/src/embeddings.ts +244 -0
- package/src/eval/index.ts +8 -0
- package/src/eval/models.ts +158 -0
- package/src/eval/runner.ts +217 -0
- package/src/generate.ts +245 -0
- package/src/index.ts +154 -0
- package/src/primitives.ts +612 -0
- package/src/providers/cloudflare.ts +15 -0
- package/src/providers/index.ts +14 -0
- package/src/schema.ts +147 -0
- package/src/template.ts +209 -0
- package/src/types.ts +540 -0
- package/test/README.md +105 -0
- package/test/ai-proxy.test.ts +192 -0
- package/test/async-iterators.test.ts +327 -0
- package/test/batch-background.test.ts +482 -0
- package/test/batch-blog-posts.test.ts +387 -0
- package/test/blog-generation.test.ts +510 -0
- package/test/browse-read.test.ts +611 -0
- package/test/core-functions.test.ts +694 -0
- package/test/decide.test.ts +393 -0
- package/test/define.test.ts +274 -0
- package/test/e2e-bedrock-manual.ts +163 -0
- package/test/e2e-bedrock.test.ts +191 -0
- package/test/e2e-flex-gateway.ts +157 -0
- package/test/e2e-flex-manual.ts +183 -0
- package/test/e2e-flex.test.ts +209 -0
- package/test/e2e-google-manual.ts +178 -0
- package/test/e2e-google.test.ts +216 -0
- package/test/embeddings.test.ts +284 -0
- package/test/evals/define-function.eval.test.ts +379 -0
- package/test/evals/primitives.eval.test.ts +384 -0
- package/test/function-types.test.ts +492 -0
- package/test/generate-core.test.ts +319 -0
- package/test/generate.test.ts +163 -0
- package/test/implicit-batch.test.ts +422 -0
- package/test/schema.test.ts +109 -0
- package/test/tagged-templates.test.ts +302 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +42 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -5
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -38
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.js +0 -35
- package/dist/cli/index.test.js.map +0 -1
- package/dist/constants/models.d.ts +0 -10
- package/dist/constants/models.d.ts.map +0 -1
- package/dist/constants/models.js +0 -12
- package/dist/constants/models.js.map +0 -1
- package/dist/converters/index.d.ts +0 -3
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -3
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/model.d.ts +0 -4
- package/dist/converters/model.d.ts.map +0 -1
- package/dist/converters/model.js +0 -19
- package/dist/converters/model.js.map +0 -1
- package/dist/converters/schema.d.ts +0 -4
- package/dist/converters/schema.d.ts.map +0 -1
- package/dist/converters/schema.js +0 -25
- package/dist/converters/schema.js.map +0 -1
- package/dist/core/responses.d.ts +0 -5
- package/dist/core/responses.d.ts.map +0 -1
- package/dist/core/responses.js +0 -16
- package/dist/core/responses.js.map +0 -1
- package/dist/core/responses.test.d.ts +0 -2
- package/dist/core/responses.test.d.ts.map +0 -1
- package/dist/core/responses.test.js +0 -31
- package/dist/core/responses.test.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -9
- package/dist/errors.js.map +0 -1
- package/dist/examples/streaming.test.d.ts +0 -2
- package/dist/examples/streaming.test.d.ts.map +0 -1
- package/dist/examples/streaming.test.js +0 -176
- package/dist/examples/streaming.test.js.map +0 -1
- package/dist/factory/__tests__/index.test.d.ts +0 -2
- package/dist/factory/__tests__/index.test.d.ts.map +0 -1
- package/dist/factory/__tests__/index.test.js +0 -430
- package/dist/factory/__tests__/index.test.js.map +0 -1
- package/dist/factory/__tests__/list.test.d.ts +0 -2
- package/dist/factory/__tests__/list.test.d.ts.map +0 -1
- package/dist/factory/__tests__/list.test.js +0 -92
- package/dist/factory/__tests__/list.test.js.map +0 -1
- package/dist/factory/index.d.ts +0 -20
- package/dist/factory/index.d.ts.map +0 -1
- package/dist/factory/index.js +0 -287
- package/dist/factory/index.js.map +0 -1
- package/dist/factory/index.test.d.ts +0 -2
- package/dist/factory/index.test.d.ts.map +0 -1
- package/dist/factory/index.test.js +0 -287
- package/dist/factory/index.test.js.map +0 -1
- package/dist/factory/list.d.ts +0 -3
- package/dist/factory/list.d.ts.map +0 -1
- package/dist/factory/list.js +0 -221
- package/dist/factory/list.js.map +0 -1
- package/dist/factory/list.test.d.ts +0 -2
- package/dist/factory/list.test.d.ts.map +0 -1
- package/dist/factory/list.test.js +0 -84
- package/dist/factory/list.test.js.map +0 -1
- package/dist/generate/index.d.ts +0 -5
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -17
- package/dist/generate/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/list/await.d.ts +0 -3
- package/dist/list/await.d.ts.map +0 -1
- package/dist/list/await.js +0 -28
- package/dist/list/await.js.map +0 -1
- package/dist/list/constants.d.ts +0 -4
- package/dist/list/constants.d.ts.map +0 -1
- package/dist/list/constants.js +0 -5
- package/dist/list/constants.js.map +0 -1
- package/dist/list/create-function.d.ts +0 -3
- package/dist/list/create-function.d.ts.map +0 -1
- package/dist/list/create-function.js +0 -11
- package/dist/list/create-function.js.map +0 -1
- package/dist/list/index.d.ts +0 -4
- package/dist/list/index.d.ts.map +0 -1
- package/dist/list/index.js +0 -5
- package/dist/list/index.js.map +0 -1
- package/dist/list/prompt.d.ts +0 -3
- package/dist/list/prompt.d.ts.map +0 -1
- package/dist/list/prompt.js +0 -6
- package/dist/list/prompt.js.map +0 -1
- package/dist/list/schemas.d.ts +0 -4
- package/dist/list/schemas.d.ts.map +0 -1
- package/dist/list/schemas.js +0 -8
- package/dist/list/schemas.js.map +0 -1
- package/dist/list/stream.d.ts +0 -3
- package/dist/list/stream.d.ts.map +0 -1
- package/dist/list/stream.js +0 -33
- package/dist/list/stream.js.map +0 -1
- package/dist/list/types.d.ts +0 -11
- package/dist/list/types.d.ts.map +0 -1
- package/dist/list/types.js +0 -2
- package/dist/list/types.js.map +0 -1
- package/dist/list/validation.d.ts +0 -3
- package/dist/list/validation.d.ts.map +0 -1
- package/dist/list/validation.js +0 -12
- package/dist/list/validation.js.map +0 -1
- package/dist/providers/config.d.ts +0 -4
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -21
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -2
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -37
- package/dist/providers/config.test.js.map +0 -1
- package/dist/proxy/constants.d.ts +0 -4
- package/dist/proxy/constants.d.ts.map +0 -1
- package/dist/proxy/constants.js +0 -5
- package/dist/proxy/constants.js.map +0 -1
- package/dist/proxy/create-function.d.ts +0 -4
- package/dist/proxy/create-function.d.ts.map +0 -1
- package/dist/proxy/create-function.js +0 -24
- package/dist/proxy/create-function.js.map +0 -1
- package/dist/proxy/create-proxy.d.ts +0 -2
- package/dist/proxy/create-proxy.d.ts.map +0 -1
- package/dist/proxy/create-proxy.js +0 -11
- package/dist/proxy/create-proxy.js.map +0 -1
- package/dist/proxy/function-generator.d.ts +0 -9
- package/dist/proxy/function-generator.d.ts.map +0 -1
- package/dist/proxy/function-generator.js +0 -29
- package/dist/proxy/function-generator.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -4
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -4
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/prompt.d.ts +0 -2
- package/dist/proxy/prompt.d.ts.map +0 -1
- package/dist/proxy/prompt.js +0 -6
- package/dist/proxy/prompt.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -7
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -2
- package/dist/proxy/types.js.map +0 -1
- package/dist/queue/manager.d.ts +0 -5
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.js +0 -37
- package/dist/queue/manager.js.map +0 -1
- package/dist/queue/manager.test.d.ts +0 -2
- package/dist/queue/manager.test.d.ts.map +0 -1
- package/dist/queue/manager.test.js +0 -52
- package/dist/queue/manager.test.js.map +0 -1
- package/dist/schema-converter.d.ts +0 -4
- package/dist/schema-converter.d.ts.map +0 -1
- package/dist/schema-converter.js +0 -30
- package/dist/schema-converter.js.map +0 -1
- package/dist/stream/index.d.ts +0 -7
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -23
- package/dist/stream/index.js.map +0 -1
- package/dist/streaming/utils.d.ts +0 -4
- package/dist/streaming/utils.d.ts.map +0 -1
- package/dist/streaming/utils.js +0 -131
- package/dist/streaming/utils.js.map +0 -1
- package/dist/streaming/utils.test.d.ts +0 -2
- package/dist/streaming/utils.test.d.ts.map +0 -1
- package/dist/streaming/utils.test.js +0 -84
- package/dist/streaming/utils.test.js.map +0 -1
- package/dist/templates/result.d.ts +0 -7
- package/dist/templates/result.d.ts.map +0 -1
- package/dist/templates/result.js +0 -40
- package/dist/templates/result.js.map +0 -1
- package/dist/templates/result.test.d.ts +0 -2
- package/dist/templates/result.test.d.ts.map +0 -1
- package/dist/templates/result.test.js +0 -75
- package/dist/templates/result.test.js.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/test/setup.js +0 -21
- package/dist/test/setup.js.map +0 -1
- package/dist/test-types.d.ts +0 -13
- package/dist/test-types.d.ts.map +0 -1
- package/dist/test-types.js +0 -55
- package/dist/test-types.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/list.d.ts +0 -10
- package/dist/types/list.d.ts.map +0 -1
- package/dist/types/list.js +0 -2
- package/dist/types/list.js.map +0 -1
- package/dist/types/model.d.ts +0 -7
- package/dist/types/model.d.ts.map +0 -1
- package/dist/types/model.js +0 -2
- package/dist/types/model.js.map +0 -1
- package/dist/types/options.d.ts +0 -25
- package/dist/types/options.d.ts.map +0 -1
- package/dist/types/options.js +0 -2
- package/dist/types/options.js.map +0 -1
- package/dist/types/schema.d.ts +0 -5
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/types/schema.js +0 -2
- package/dist/types/schema.js.map +0 -1
- package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
- package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
- package/dist/utils/__tests__/request-handler.test.js +0 -134
- package/dist/utils/__tests__/request-handler.test.js.map +0 -1
- package/dist/utils/__tests__/schema.test.d.ts +0 -2
- package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
- package/dist/utils/__tests__/schema.test.js +0 -49
- package/dist/utils/__tests__/schema.test.js.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
- package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.js +0 -85
- package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/request-handler.d.ts +0 -17
- package/dist/utils/request-handler.d.ts.map +0 -1
- package/dist/utils/request-handler.js +0 -105
- package/dist/utils/request-handler.js.map +0 -1
- package/dist/utils/schema.d.ts +0 -11
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/schema.js +0 -51
- package/dist/utils/schema.js.map +0 -1
- package/dist/utils/stream-progress.d.ts +0 -17
- package/dist/utils/stream-progress.d.ts.map +0 -1
- package/dist/utils/stream-progress.js +0 -86
- package/dist/utils/stream-progress.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -30
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for web functions: read() and browse()
|
|
3
|
+
*
|
|
4
|
+
* read(url) - Convert URL to markdown (Firecrawl-style)
|
|
5
|
+
* browse(url) - Browser automation (Stagehand-style)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Mock implementations
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
const mockFetchUrl = vi.fn()
|
|
15
|
+
const mockBrowserSession = vi.fn()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Mock read function - converts URL to markdown
|
|
19
|
+
*/
|
|
20
|
+
async function read(
|
|
21
|
+
urlOrStrings: string | TemplateStringsArray,
|
|
22
|
+
...args: unknown[]
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
let url: string
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(urlOrStrings) && 'raw' in urlOrStrings) {
|
|
27
|
+
url = (urlOrStrings as TemplateStringsArray).reduce((acc, str, i) => {
|
|
28
|
+
return acc + str + (args[i] ?? '')
|
|
29
|
+
}, '')
|
|
30
|
+
} else {
|
|
31
|
+
url = urlOrStrings as string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return mockFetchUrl(url)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Mock browse function - returns a page context for browser automation
|
|
39
|
+
*/
|
|
40
|
+
async function browse(
|
|
41
|
+
urlOrStrings: string | TemplateStringsArray,
|
|
42
|
+
...args: unknown[]
|
|
43
|
+
): Promise<{
|
|
44
|
+
do: (action: string) => Promise<void>
|
|
45
|
+
extract: (query: string) => Promise<unknown>
|
|
46
|
+
screenshot: () => Promise<Buffer>
|
|
47
|
+
close: () => Promise<void>
|
|
48
|
+
}> {
|
|
49
|
+
let url: string
|
|
50
|
+
|
|
51
|
+
if (Array.isArray(urlOrStrings) && 'raw' in urlOrStrings) {
|
|
52
|
+
url = (urlOrStrings as TemplateStringsArray).reduce((acc, str, i) => {
|
|
53
|
+
return acc + str + (args[i] ?? '')
|
|
54
|
+
}, '')
|
|
55
|
+
} else {
|
|
56
|
+
url = urlOrStrings as string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return mockBrowserSession(url)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// read() tests
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
describe('read() - URL to Markdown', () => {
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
mockFetchUrl.mockReset()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('basic usage', () => {
|
|
72
|
+
it('converts URL to markdown', async () => {
|
|
73
|
+
mockFetchUrl.mockResolvedValue(`
|
|
74
|
+
# Page Title
|
|
75
|
+
|
|
76
|
+
Some content from the page.
|
|
77
|
+
|
|
78
|
+
## Section 1
|
|
79
|
+
|
|
80
|
+
More content here.
|
|
81
|
+
`.trim())
|
|
82
|
+
|
|
83
|
+
const content = await read('https://example.com')
|
|
84
|
+
|
|
85
|
+
expect(mockFetchUrl).toHaveBeenCalledWith('https://example.com')
|
|
86
|
+
expect(content).toContain('# Page Title')
|
|
87
|
+
expect(content).toContain('Some content')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('supports tagged template syntax', async () => {
|
|
91
|
+
mockFetchUrl.mockResolvedValue('# Content')
|
|
92
|
+
|
|
93
|
+
const domain = 'example.com'
|
|
94
|
+
const path = '/docs'
|
|
95
|
+
const content = await read`https://${domain}${path}`
|
|
96
|
+
|
|
97
|
+
expect(mockFetchUrl).toHaveBeenCalledWith('https://example.com/docs')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('extracts main content, strips navigation/ads', async () => {
|
|
101
|
+
mockFetchUrl.mockResolvedValue(`
|
|
102
|
+
# Article Title
|
|
103
|
+
|
|
104
|
+
This is the main article content, clean and focused.
|
|
105
|
+
`.trim())
|
|
106
|
+
|
|
107
|
+
const content = await read('https://blog.example.com/article')
|
|
108
|
+
|
|
109
|
+
// Extracted content should be clean without HTML tags
|
|
110
|
+
expect(content).not.toContain('<nav>')
|
|
111
|
+
expect(content).not.toContain('<!-- advertisement -->')
|
|
112
|
+
expect(content).toContain('Article Title')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('content extraction', () => {
|
|
117
|
+
it('preserves headers and structure', async () => {
|
|
118
|
+
mockFetchUrl.mockResolvedValue(`
|
|
119
|
+
# Main Title
|
|
120
|
+
|
|
121
|
+
## Introduction
|
|
122
|
+
|
|
123
|
+
Intro paragraph.
|
|
124
|
+
|
|
125
|
+
## Details
|
|
126
|
+
|
|
127
|
+
Detail paragraph.
|
|
128
|
+
|
|
129
|
+
### Subsection
|
|
130
|
+
|
|
131
|
+
More details.
|
|
132
|
+
`.trim())
|
|
133
|
+
|
|
134
|
+
const content = await read('https://docs.example.com')
|
|
135
|
+
|
|
136
|
+
expect(content).toContain('# Main Title')
|
|
137
|
+
expect(content).toContain('## Introduction')
|
|
138
|
+
expect(content).toContain('### Subsection')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('converts links to markdown format', async () => {
|
|
142
|
+
mockFetchUrl.mockResolvedValue(
|
|
143
|
+
'Check out [our documentation](https://docs.example.com) for more info.'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const content = await read('https://example.com')
|
|
147
|
+
|
|
148
|
+
expect(content).toContain('[our documentation](https://docs.example.com)')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('handles code blocks', async () => {
|
|
152
|
+
mockFetchUrl.mockResolvedValue(`
|
|
153
|
+
Here's an example:
|
|
154
|
+
|
|
155
|
+
\`\`\`typescript
|
|
156
|
+
const x = 1;
|
|
157
|
+
\`\`\`
|
|
158
|
+
`.trim())
|
|
159
|
+
|
|
160
|
+
const content = await read('https://tutorial.example.com')
|
|
161
|
+
|
|
162
|
+
expect(content).toContain('```typescript')
|
|
163
|
+
expect(content).toContain('const x = 1;')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('handles tables', async () => {
|
|
167
|
+
mockFetchUrl.mockResolvedValue(`
|
|
168
|
+
| Header 1 | Header 2 |
|
|
169
|
+
|----------|----------|
|
|
170
|
+
| Cell 1 | Cell 2 |
|
|
171
|
+
`.trim())
|
|
172
|
+
|
|
173
|
+
const content = await read('https://data.example.com')
|
|
174
|
+
|
|
175
|
+
expect(content).toContain('| Header 1 |')
|
|
176
|
+
expect(content).toContain('| Cell 1 |')
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('use cases', () => {
|
|
181
|
+
it('reads documentation for research', async () => {
|
|
182
|
+
mockFetchUrl.mockResolvedValue(`
|
|
183
|
+
# API Reference
|
|
184
|
+
|
|
185
|
+
## Authentication
|
|
186
|
+
|
|
187
|
+
Use Bearer tokens for authentication.
|
|
188
|
+
|
|
189
|
+
## Endpoints
|
|
190
|
+
|
|
191
|
+
### GET /users
|
|
192
|
+
|
|
193
|
+
Returns list of users.
|
|
194
|
+
`.trim())
|
|
195
|
+
|
|
196
|
+
const docs = await read('https://api.example.com/docs')
|
|
197
|
+
|
|
198
|
+
expect(docs).toContain('API Reference')
|
|
199
|
+
expect(docs).toContain('Authentication')
|
|
200
|
+
expect(docs).toContain('GET /users')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('reads articles for summarization', async () => {
|
|
204
|
+
mockFetchUrl.mockResolvedValue(`
|
|
205
|
+
# The Future of AI
|
|
206
|
+
|
|
207
|
+
Artificial intelligence is transforming industries...
|
|
208
|
+
|
|
209
|
+
## Impact on Healthcare
|
|
210
|
+
|
|
211
|
+
AI is revolutionizing medical diagnosis...
|
|
212
|
+
|
|
213
|
+
## Impact on Finance
|
|
214
|
+
|
|
215
|
+
Automated trading systems...
|
|
216
|
+
`.trim())
|
|
217
|
+
|
|
218
|
+
const article = await read('https://news.example.com/ai-future')
|
|
219
|
+
|
|
220
|
+
expect(article).toContain('Future of AI')
|
|
221
|
+
expect(article).toContain('Impact on Healthcare')
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// browse() tests
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
describe('browse() - Browser Automation', () => {
|
|
231
|
+
beforeEach(() => {
|
|
232
|
+
mockBrowserSession.mockReset()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
describe('page context', () => {
|
|
236
|
+
it('returns page object with do/extract methods', async () => {
|
|
237
|
+
const mockPage = {
|
|
238
|
+
do: vi.fn(),
|
|
239
|
+
extract: vi.fn(),
|
|
240
|
+
screenshot: vi.fn(),
|
|
241
|
+
close: vi.fn(),
|
|
242
|
+
}
|
|
243
|
+
mockBrowserSession.mockResolvedValue(mockPage)
|
|
244
|
+
|
|
245
|
+
const page = await browse('https://example.com')
|
|
246
|
+
|
|
247
|
+
expect(page).toHaveProperty('do')
|
|
248
|
+
expect(page).toHaveProperty('extract')
|
|
249
|
+
expect(page).toHaveProperty('screenshot')
|
|
250
|
+
expect(page).toHaveProperty('close')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('supports tagged template syntax', async () => {
|
|
254
|
+
mockBrowserSession.mockResolvedValue({
|
|
255
|
+
do: vi.fn(),
|
|
256
|
+
extract: vi.fn(),
|
|
257
|
+
screenshot: vi.fn(),
|
|
258
|
+
close: vi.fn(),
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const domain = 'example.com'
|
|
262
|
+
await browse`https://${domain}`
|
|
263
|
+
|
|
264
|
+
expect(mockBrowserSession).toHaveBeenCalledWith('https://example.com')
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('page.do() - actions', () => {
|
|
269
|
+
it('performs click actions', async () => {
|
|
270
|
+
const mockDo = vi.fn()
|
|
271
|
+
mockBrowserSession.mockResolvedValue({
|
|
272
|
+
do: mockDo,
|
|
273
|
+
extract: vi.fn(),
|
|
274
|
+
screenshot: vi.fn(),
|
|
275
|
+
close: vi.fn(),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const page = await browse('https://app.example.com')
|
|
279
|
+
await page.do('click the login button')
|
|
280
|
+
|
|
281
|
+
expect(mockDo).toHaveBeenCalledWith('click the login button')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('performs form filling', async () => {
|
|
285
|
+
const mockDo = vi.fn()
|
|
286
|
+
mockBrowserSession.mockResolvedValue({
|
|
287
|
+
do: mockDo,
|
|
288
|
+
extract: vi.fn(),
|
|
289
|
+
screenshot: vi.fn(),
|
|
290
|
+
close: vi.fn(),
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const page = await browse('https://app.example.com/login')
|
|
294
|
+
await page.do('fill in the email field with test@example.com')
|
|
295
|
+
await page.do('fill in the password field with password123')
|
|
296
|
+
await page.do('click submit')
|
|
297
|
+
|
|
298
|
+
expect(mockDo).toHaveBeenCalledTimes(3)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('performs navigation actions', async () => {
|
|
302
|
+
const mockDo = vi.fn()
|
|
303
|
+
mockBrowserSession.mockResolvedValue({
|
|
304
|
+
do: mockDo,
|
|
305
|
+
extract: vi.fn(),
|
|
306
|
+
screenshot: vi.fn(),
|
|
307
|
+
close: vi.fn(),
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const page = await browse('https://app.example.com')
|
|
311
|
+
await page.do('click on Settings in the navigation menu')
|
|
312
|
+
await page.do('scroll to the bottom of the page')
|
|
313
|
+
|
|
314
|
+
expect(mockDo).toHaveBeenNthCalledWith(1, 'click on Settings in the navigation menu')
|
|
315
|
+
expect(mockDo).toHaveBeenNthCalledWith(2, 'scroll to the bottom of the page')
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe('page.extract() - data extraction', () => {
|
|
320
|
+
it('extracts text content', async () => {
|
|
321
|
+
const mockExtract = vi.fn().mockResolvedValue('Welcome, John Doe')
|
|
322
|
+
mockBrowserSession.mockResolvedValue({
|
|
323
|
+
do: vi.fn(),
|
|
324
|
+
extract: mockExtract,
|
|
325
|
+
screenshot: vi.fn(),
|
|
326
|
+
close: vi.fn(),
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
const page = await browse('https://app.example.com/dashboard')
|
|
330
|
+
const username = await page.extract('the username in the header')
|
|
331
|
+
|
|
332
|
+
expect(mockExtract).toHaveBeenCalledWith('the username in the header')
|
|
333
|
+
expect(username).toBe('Welcome, John Doe')
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('extracts structured data', async () => {
|
|
337
|
+
const mockExtract = vi.fn().mockResolvedValue([
|
|
338
|
+
{ name: 'Product A', price: 29.99 },
|
|
339
|
+
{ name: 'Product B', price: 49.99 },
|
|
340
|
+
])
|
|
341
|
+
mockBrowserSession.mockResolvedValue({
|
|
342
|
+
do: vi.fn(),
|
|
343
|
+
extract: mockExtract,
|
|
344
|
+
screenshot: vi.fn(),
|
|
345
|
+
close: vi.fn(),
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
const page = await browse('https://shop.example.com/products')
|
|
349
|
+
const products = await page.extract('all products with their names and prices')
|
|
350
|
+
|
|
351
|
+
expect(products).toHaveLength(2)
|
|
352
|
+
expect(products[0]).toHaveProperty('name', 'Product A')
|
|
353
|
+
expect(products[0]).toHaveProperty('price', 29.99)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('extracts table data', async () => {
|
|
357
|
+
const mockExtract = vi.fn().mockResolvedValue([
|
|
358
|
+
{ date: '2024-01-01', amount: 100, status: 'completed' },
|
|
359
|
+
{ date: '2024-01-02', amount: 200, status: 'pending' },
|
|
360
|
+
])
|
|
361
|
+
mockBrowserSession.mockResolvedValue({
|
|
362
|
+
do: vi.fn(),
|
|
363
|
+
extract: mockExtract,
|
|
364
|
+
screenshot: vi.fn(),
|
|
365
|
+
close: vi.fn(),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const page = await browse('https://app.example.com/transactions')
|
|
369
|
+
const transactions = await page.extract('the transaction table data')
|
|
370
|
+
|
|
371
|
+
expect(transactions).toHaveLength(2)
|
|
372
|
+
expect(transactions[0]).toHaveProperty('date')
|
|
373
|
+
expect(transactions[0]).toHaveProperty('amount')
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
describe('page.screenshot()', () => {
|
|
378
|
+
it('captures page screenshot', async () => {
|
|
379
|
+
const mockScreenshot = vi.fn().mockResolvedValue(Buffer.from('fake-image'))
|
|
380
|
+
mockBrowserSession.mockResolvedValue({
|
|
381
|
+
do: vi.fn(),
|
|
382
|
+
extract: vi.fn(),
|
|
383
|
+
screenshot: mockScreenshot,
|
|
384
|
+
close: vi.fn(),
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
const page = await browse('https://example.com')
|
|
388
|
+
const screenshot = await page.screenshot()
|
|
389
|
+
|
|
390
|
+
expect(mockScreenshot).toHaveBeenCalled()
|
|
391
|
+
expect(Buffer.isBuffer(screenshot)).toBe(true)
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
describe('page.close()', () => {
|
|
396
|
+
it('closes browser session', async () => {
|
|
397
|
+
const mockClose = vi.fn()
|
|
398
|
+
mockBrowserSession.mockResolvedValue({
|
|
399
|
+
do: vi.fn(),
|
|
400
|
+
extract: vi.fn(),
|
|
401
|
+
screenshot: vi.fn(),
|
|
402
|
+
close: mockClose,
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
const page = await browse('https://example.com')
|
|
406
|
+
await page.close()
|
|
407
|
+
|
|
408
|
+
expect(mockClose).toHaveBeenCalled()
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// Combined read + browse workflows
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
describe('combined workflows', () => {
|
|
418
|
+
beforeEach(() => {
|
|
419
|
+
mockFetchUrl.mockReset()
|
|
420
|
+
mockBrowserSession.mockReset()
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('read for static content, browse for dynamic', async () => {
|
|
424
|
+
// Use read for documentation
|
|
425
|
+
mockFetchUrl.mockResolvedValue('# API Docs\n\nAuthentication required.')
|
|
426
|
+
const docs = await read('https://api.example.com/docs')
|
|
427
|
+
expect(docs).toContain('API Docs')
|
|
428
|
+
|
|
429
|
+
// Use browse for interactive testing
|
|
430
|
+
const mockPage = {
|
|
431
|
+
do: vi.fn(),
|
|
432
|
+
extract: vi.fn().mockResolvedValue({ status: 'success' }),
|
|
433
|
+
screenshot: vi.fn(),
|
|
434
|
+
close: vi.fn(),
|
|
435
|
+
}
|
|
436
|
+
mockBrowserSession.mockResolvedValue(mockPage)
|
|
437
|
+
|
|
438
|
+
const page = await browse('https://api.example.com/playground')
|
|
439
|
+
await page.do('enter API key in the auth field')
|
|
440
|
+
await page.do('click send request')
|
|
441
|
+
const result = await page.extract('the response status')
|
|
442
|
+
|
|
443
|
+
expect(result).toEqual({ status: 'success' })
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('research workflow: read docs, browse to verify', async () => {
|
|
447
|
+
// Step 1: Read documentation
|
|
448
|
+
mockFetchUrl.mockResolvedValue(`
|
|
449
|
+
# Getting Started
|
|
450
|
+
|
|
451
|
+
1. Sign up at example.com/signup
|
|
452
|
+
2. Get your API key from settings
|
|
453
|
+
3. Make your first request
|
|
454
|
+
`.trim())
|
|
455
|
+
|
|
456
|
+
const docs = await read('https://example.com/docs')
|
|
457
|
+
expect(docs).toContain('Sign up')
|
|
458
|
+
|
|
459
|
+
// Step 2: Browse to verify signup flow
|
|
460
|
+
const mockPage = {
|
|
461
|
+
do: vi.fn(),
|
|
462
|
+
extract: vi.fn(),
|
|
463
|
+
screenshot: vi.fn(),
|
|
464
|
+
close: vi.fn(),
|
|
465
|
+
}
|
|
466
|
+
mockBrowserSession.mockResolvedValue(mockPage)
|
|
467
|
+
|
|
468
|
+
const page = await browse('https://example.com/signup')
|
|
469
|
+
await page.do('fill in email with test@test.com')
|
|
470
|
+
await page.do('fill in password with testpass123')
|
|
471
|
+
await page.do('click sign up button')
|
|
472
|
+
|
|
473
|
+
expect(mockPage.do).toHaveBeenCalledTimes(3)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('competitive analysis: read multiple sources', async () => {
|
|
477
|
+
const competitors = ['competitor1.com', 'competitor2.com', 'competitor3.com']
|
|
478
|
+
|
|
479
|
+
mockFetchUrl
|
|
480
|
+
.mockResolvedValueOnce('# Competitor 1\n\nPricing: $10/mo')
|
|
481
|
+
.mockResolvedValueOnce('# Competitor 2\n\nPricing: $15/mo')
|
|
482
|
+
.mockResolvedValueOnce('# Competitor 3\n\nPricing: $20/mo')
|
|
483
|
+
|
|
484
|
+
const analyses = await Promise.all(
|
|
485
|
+
competitors.map(c => read(`https://${c}/pricing`))
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
expect(analyses).toHaveLength(3)
|
|
489
|
+
expect(analyses[0]).toContain('$10/mo')
|
|
490
|
+
expect(analyses[1]).toContain('$15/mo')
|
|
491
|
+
expect(analyses[2]).toContain('$20/mo')
|
|
492
|
+
})
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// Error handling
|
|
497
|
+
// ============================================================================
|
|
498
|
+
|
|
499
|
+
describe('error handling', () => {
|
|
500
|
+
beforeEach(() => {
|
|
501
|
+
mockFetchUrl.mockReset()
|
|
502
|
+
mockBrowserSession.mockReset()
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
describe('read() errors', () => {
|
|
506
|
+
it('handles 404 errors', async () => {
|
|
507
|
+
mockFetchUrl.mockRejectedValue(new Error('404 Not Found'))
|
|
508
|
+
|
|
509
|
+
await expect(read('https://example.com/nonexistent')).rejects.toThrow('404')
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
it('handles network errors', async () => {
|
|
513
|
+
mockFetchUrl.mockRejectedValue(new Error('Network error'))
|
|
514
|
+
|
|
515
|
+
await expect(read('https://unreachable.example.com')).rejects.toThrow('Network')
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
it('handles timeout errors', async () => {
|
|
519
|
+
mockFetchUrl.mockRejectedValue(new Error('Timeout'))
|
|
520
|
+
|
|
521
|
+
await expect(read('https://slow.example.com')).rejects.toThrow('Timeout')
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
describe('browse() errors', () => {
|
|
526
|
+
it('handles page load failures', async () => {
|
|
527
|
+
mockBrowserSession.mockRejectedValue(new Error('Page failed to load'))
|
|
528
|
+
|
|
529
|
+
await expect(browse('https://broken.example.com')).rejects.toThrow('failed to load')
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('handles action failures', async () => {
|
|
533
|
+
const mockDo = vi.fn().mockRejectedValue(new Error('Element not found'))
|
|
534
|
+
mockBrowserSession.mockResolvedValue({
|
|
535
|
+
do: mockDo,
|
|
536
|
+
extract: vi.fn(),
|
|
537
|
+
screenshot: vi.fn(),
|
|
538
|
+
close: vi.fn(),
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
const page = await browse('https://app.example.com')
|
|
542
|
+
await expect(page.do('click nonexistent button')).rejects.toThrow('not found')
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it('handles extraction failures', async () => {
|
|
546
|
+
const mockExtract = vi.fn().mockRejectedValue(new Error('Cannot find element'))
|
|
547
|
+
mockBrowserSession.mockResolvedValue({
|
|
548
|
+
do: vi.fn(),
|
|
549
|
+
extract: mockExtract,
|
|
550
|
+
screenshot: vi.fn(),
|
|
551
|
+
close: vi.fn(),
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
const page = await browse('https://app.example.com')
|
|
555
|
+
await expect(page.extract('nonexistent element')).rejects.toThrow('Cannot find')
|
|
556
|
+
})
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
// ============================================================================
|
|
561
|
+
// Options
|
|
562
|
+
// ============================================================================
|
|
563
|
+
|
|
564
|
+
describe('options', () => {
|
|
565
|
+
it('read supports timeout option', async () => {
|
|
566
|
+
const mockReadWithOptions = vi.fn().mockResolvedValue('content')
|
|
567
|
+
|
|
568
|
+
await mockReadWithOptions('https://example.com', { timeout: 5000 })
|
|
569
|
+
|
|
570
|
+
expect(mockReadWithOptions).toHaveBeenCalledWith(
|
|
571
|
+
'https://example.com',
|
|
572
|
+
expect.objectContaining({ timeout: 5000 })
|
|
573
|
+
)
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
it('browse supports headless option', async () => {
|
|
577
|
+
const mockBrowseWithOptions = vi.fn().mockResolvedValue({
|
|
578
|
+
do: vi.fn(),
|
|
579
|
+
extract: vi.fn(),
|
|
580
|
+
screenshot: vi.fn(),
|
|
581
|
+
close: vi.fn(),
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
await mockBrowseWithOptions('https://example.com', { headless: false })
|
|
585
|
+
|
|
586
|
+
expect(mockBrowseWithOptions).toHaveBeenCalledWith(
|
|
587
|
+
'https://example.com',
|
|
588
|
+
expect.objectContaining({ headless: false })
|
|
589
|
+
)
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('browse supports viewport option', async () => {
|
|
593
|
+
const mockBrowseWithOptions = vi.fn().mockResolvedValue({
|
|
594
|
+
do: vi.fn(),
|
|
595
|
+
extract: vi.fn(),
|
|
596
|
+
screenshot: vi.fn(),
|
|
597
|
+
close: vi.fn(),
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
await mockBrowseWithOptions('https://example.com', {
|
|
601
|
+
viewport: { width: 1920, height: 1080 },
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
expect(mockBrowseWithOptions).toHaveBeenCalledWith(
|
|
605
|
+
'https://example.com',
|
|
606
|
+
expect.objectContaining({
|
|
607
|
+
viewport: { width: 1920, height: 1080 },
|
|
608
|
+
})
|
|
609
|
+
)
|
|
610
|
+
})
|
|
611
|
+
})
|