sunpeak 0.18.14 → 0.19.2

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.
Files changed (53) hide show
  1. package/README.md +37 -134
  2. package/bin/commands/new.mjs +3 -1
  3. package/bin/commands/test-init.mjs +305 -0
  4. package/bin/commands/test.mjs +144 -0
  5. package/bin/lib/inspect/inspect-config.d.mts +4 -0
  6. package/bin/lib/inspect/inspect-config.mjs +18 -24
  7. package/bin/lib/test/base-config.mjs +75 -0
  8. package/bin/lib/test/matchers.mjs +99 -0
  9. package/bin/lib/test/test-config.d.mts +66 -0
  10. package/bin/lib/test/test-config.mjs +125 -0
  11. package/bin/lib/test/test-fixtures.d.mts +129 -0
  12. package/bin/lib/test/test-fixtures.mjs +232 -0
  13. package/bin/sunpeak.js +18 -5
  14. package/package.json +22 -10
  15. package/template/README.md +18 -8
  16. package/template/dist/albums/albums.json +1 -1
  17. package/template/dist/carousel/carousel.json +1 -1
  18. package/template/dist/map/map.html +468 -280
  19. package/template/dist/map/map.json +1 -1
  20. package/template/dist/review/review.json +1 -1
  21. package/template/node_modules/.bin/playwright +2 -2
  22. package/template/node_modules/.bin/vite +2 -2
  23. package/template/node_modules/.bin/vitest +2 -2
  24. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  25. package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
  26. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js +15924 -14588
  27. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js.map +1 -1
  28. package/template/node_modules/.vite-mcp/deps/vitest.js +8 -8
  29. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  30. package/template/package.json +9 -7
  31. package/template/playwright.config.ts +2 -40
  32. package/template/test-results/.last-run.json +4 -0
  33. package/template/tests/e2e/albums.spec.ts +114 -245
  34. package/template/tests/e2e/carousel.spec.ts +189 -313
  35. package/template/tests/e2e/map.spec.ts +177 -300
  36. package/template/tests/e2e/review.spec.ts +232 -423
  37. package/template/tests/e2e/visual.spec.ts +36 -0
  38. package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-chatgpt-linux.png +0 -0
  39. package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-claude-linux.png +0 -0
  40. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-linux.png +0 -0
  41. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-linux.png +0 -0
  42. package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-chatgpt-linux.png +0 -0
  43. package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-claude-linux.png +0 -0
  44. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-chatgpt-linux.png +0 -0
  45. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-claude-linux.png +0 -0
  46. package/template/tests/live/albums.spec.ts +1 -1
  47. package/template/tests/live/carousel.spec.ts +1 -1
  48. package/template/tests/live/map.spec.ts +1 -1
  49. package/template/tests/live/playwright.config.ts +1 -1
  50. package/template/tests/live/review.spec.ts +1 -1
  51. package/template/vitest.config.ts +1 -1
  52. package/template/tests/e2e/global-setup.ts +0 -10
  53. package/template/tests/e2e/helpers.ts +0 -13
@@ -2,16 +2,19 @@
2
2
  * Playwright config factory for inspect mode (BYOS — Bring Your Own Server).
3
3
  *
4
4
  * Generates a complete Playwright config that starts `sunpeak inspect` as the
5
- * webServer and runs e2e tests against the inspector. Follows the same pattern
6
- * as `defineLiveConfig` for live tests.
5
+ * webServer and runs e2e tests against the inspector.
7
6
  *
8
7
  * Usage in playwright.config.ts:
9
8
  * import { defineInspectConfig } from 'sunpeak/test/inspect/config';
10
9
  * export default defineInspectConfig({
11
10
  * server: 'http://localhost:8000/mcp',
12
11
  * });
12
+ *
13
+ * Note: For new projects, prefer `defineConfig` from 'sunpeak/test/config'
14
+ * which auto-detects the project type and handles both sunpeak projects
15
+ * and external servers.
13
16
  */
14
- import { getPortSync } from '../get-port.mjs';
17
+ import { createBaseConfig, resolvePorts } from '../test/base-config.mjs';
15
18
 
16
19
  /**
17
20
  * Create a complete Playwright config for testing an external MCP server.
@@ -19,7 +22,7 @@ import { getPortSync } from '../get-port.mjs';
19
22
  * @param {Object} options
20
23
  * @param {string} options.server - MCP server URL or stdio command (required)
21
24
  * @param {string} [options.testDir='tests/e2e'] - Test directory
22
- * @param {string} [options.simulationsDir='tests/simulations'] - Simulation JSON directory
25
+ * @param {string} [options.simulationsDir] - Simulation JSON directory
23
26
  * @param {string[]} [options.hosts=['chatgpt', 'claude']] - Host shells to test
24
27
  * @param {string} [options.name] - App name in inspector chrome
25
28
  * @param {Object} [options.use] - Additional Playwright `use` options
@@ -33,18 +36,19 @@ export function defineInspectConfig(options) {
33
36
  hosts = ['chatgpt', 'claude'],
34
37
  name,
35
38
  use: userUse,
39
+ visual,
36
40
  } = options;
37
41
 
38
42
  if (!server) {
39
43
  throw new Error('defineInspectConfig: `server` option is required');
40
44
  }
41
45
 
42
- const port = Number(process.env.SUNPEAK_TEST_PORT) || getPortSync(6776);
43
- const sandboxPort = Number(process.env.SUNPEAK_SANDBOX_PORT) || getPortSync(24680);
46
+ const { port, sandboxPort } = resolvePorts();
44
47
 
45
48
  // Build the sunpeak inspect command
46
49
  const serverArg = server.includes(' ') ? `"${server}"` : server;
47
50
  const command = [
51
+ `SUNPEAK_SANDBOX_PORT=${sandboxPort}`,
48
52
  'npx sunpeak inspect',
49
53
  `--server ${serverArg}`,
50
54
  ...(simulationsDir ? [`--simulations ${simulationsDir}`] : []),
@@ -52,25 +56,15 @@ export function defineInspectConfig(options) {
52
56
  ...(name ? [`--name "${name}"`] : []),
53
57
  ].join(' ');
54
58
 
55
- return {
59
+ return createBaseConfig({
60
+ hosts,
56
61
  testDir,
57
- fullyParallel: true,
58
- forbidOnly: !!process.env.CI,
59
- retries: process.env.CI ? 2 : 1,
60
- // Limit workers to avoid overwhelming the double-iframe sandbox proxy.
61
- workers: process.env.CI ? 1 : 2,
62
- reporter: 'list',
63
- use: {
64
- baseURL: `http://localhost:${port}`,
65
- trace: 'on-first-retry',
66
- ...userUse,
67
- },
68
- projects: hosts.map((host) => ({ name: host })),
62
+ port,
63
+ use: userUse,
64
+ visual,
69
65
  webServer: {
70
- command: `SUNPEAK_SANDBOX_PORT=${sandboxPort} ${command}`,
71
- url: `http://localhost:${port}/health`,
72
- reuseExistingServer: !process.env.CI,
73
- timeout: 60_000,
66
+ command,
67
+ healthUrl: `http://localhost:${port}/health`,
74
68
  },
75
- };
69
+ });
76
70
  }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Shared Playwright config builder used by both defineConfig() (sunpeak projects)
3
+ * and defineInspectConfig() (external MCP servers).
4
+ *
5
+ * Produces a config with per-host Playwright projects, sensible defaults for
6
+ * MCP App testing, and a webServer entry to launch the inspector backend.
7
+ */
8
+ import { getPortSync } from '../get-port.mjs';
9
+
10
+ /**
11
+ * @param {Object} options
12
+ * @param {string[]} options.hosts - Host shells to create projects for
13
+ * @param {string} options.testDir - Test directory
14
+ * @param {Object} options.webServer - { command, healthUrl }
15
+ * @param {number} options.port - Inspector port
16
+ * @param {Object} [options.use] - Additional Playwright `use` options
17
+ * @param {string} [options.globalSetup] - Global setup file path
18
+ * @returns {import('@playwright/test').PlaywrightTestConfig}
19
+ */
20
+ export function createBaseConfig({ hosts, testDir, webServer, port, use, globalSetup, visual }) {
21
+ // Separate snapshot path from other visual options passed to expect.toHaveScreenshot
22
+ const { snapshotPathTemplate, ...toHaveScreenshotDefaults } = visual ?? {};
23
+
24
+ return {
25
+ ...(globalSetup ? { globalSetup } : {}),
26
+ testDir,
27
+ fullyParallel: true,
28
+ forbidOnly: !!process.env.CI,
29
+ retries: process.env.CI ? 2 : 1,
30
+ // Limit workers to avoid overwhelming the double-iframe sandbox proxy.
31
+ workers: process.env.CI ? 1 : 2,
32
+ reporter: 'list',
33
+ // Only override snapshot path when visual config is provided, to avoid
34
+ // changing Playwright's default for projects that don't use visual testing.
35
+ ...(visual
36
+ ? {
37
+ snapshotPathTemplate:
38
+ snapshotPathTemplate ??
39
+ '{testDir}/__screenshots__/{projectName}/{testFilePath}/{arg}{ext}',
40
+ }
41
+ : {}),
42
+ ...(Object.keys(toHaveScreenshotDefaults).length > 0
43
+ ? { expect: { toHaveScreenshot: toHaveScreenshotDefaults } }
44
+ : {}),
45
+ use: {
46
+ baseURL: `http://localhost:${port}`,
47
+ trace: 'on-first-retry',
48
+ ...use,
49
+ },
50
+ projects: hosts.map((host) => ({ name: host })),
51
+ webServer: {
52
+ command: webServer.command,
53
+ url: webServer.healthUrl,
54
+ reuseExistingServer: !process.env.CI,
55
+ timeout: 60_000,
56
+ },
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Resolve ports for the inspector and sandbox proxy.
62
+ * Respects env vars for CI where validate.mjs assigns unique ports.
63
+ */
64
+ export function resolvePorts() {
65
+ const port = parsePort(process.env.SUNPEAK_TEST_PORT) ?? getPortSync(6776);
66
+ const sandboxPort = parsePort(process.env.SUNPEAK_SANDBOX_PORT) ?? getPortSync(24680);
67
+ return { port, sandboxPort };
68
+ }
69
+
70
+ /** Parse a port string, returning the number or null if invalid/absent. */
71
+ function parsePort(value) {
72
+ if (value == null) return null;
73
+ const n = Number(value);
74
+ return Number.isFinite(n) && n > 0 ? n : null;
75
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * MCP-native custom matchers for Playwright's expect.
3
+ *
4
+ * These matchers operate on ToolResult objects returned by mcp.callTool()
5
+ * and provide MCP-concept-native assertions.
6
+ */
7
+
8
+ /**
9
+ * Register MCP matchers on a Playwright expect instance.
10
+ * @param {import('@playwright/test').Expect} expect
11
+ */
12
+ export function registerMatchers(expect) {
13
+ expect.extend({
14
+ /**
15
+ * Assert that a tool result is an error.
16
+ * Usage: expect(result).toBeError()
17
+ */
18
+ toBeError(received) {
19
+ const pass = received?.isError === true;
20
+ return {
21
+ pass,
22
+ message: () =>
23
+ pass
24
+ ? `Expected tool result not to be an error, but it was`
25
+ : `Expected tool result to be an error, but isError was ${received?.isError}`,
26
+ };
27
+ },
28
+
29
+ /**
30
+ * Assert that any content item's text contains the given string.
31
+ * Usage: expect(result).toHaveTextContent('temperature')
32
+ */
33
+ toHaveTextContent(received, expected) {
34
+ const content = received?.content || [];
35
+ const texts = content
36
+ .filter((c) => c.type === 'text' && typeof c.text === 'string')
37
+ .map((c) => c.text);
38
+ const pass = texts.some((t) => t.includes(expected));
39
+ return {
40
+ pass,
41
+ message: () =>
42
+ pass
43
+ ? `Expected tool result not to contain text "${expected}", but found it`
44
+ : `Expected tool result to contain text "${expected}" in content items.\nFound texts: ${JSON.stringify(texts)}`,
45
+ };
46
+ },
47
+
48
+ /**
49
+ * Assert that structuredContent matches the expected shape (deep partial match).
50
+ * Usage: expect(result).toHaveStructuredContent({ type: 'weather' })
51
+ */
52
+ toHaveStructuredContent(received, expected) {
53
+ const sc = received?.structuredContent;
54
+ const pass = sc !== undefined && deepPartialMatch(sc, expected);
55
+ return {
56
+ pass,
57
+ message: () =>
58
+ pass
59
+ ? `Expected structuredContent not to match, but it did`
60
+ : `Expected structuredContent to match ${JSON.stringify(expected)}, got ${JSON.stringify(sc)}`,
61
+ };
62
+ },
63
+
64
+ /**
65
+ * Assert that content array contains an item of the given type.
66
+ * Usage: expect(result).toHaveContentType('image')
67
+ */
68
+ toHaveContentType(received, expectedType) {
69
+ const content = received?.content || [];
70
+ const types = content.map((c) => c.type);
71
+ const pass = types.includes(expectedType);
72
+ return {
73
+ pass,
74
+ message: () =>
75
+ pass
76
+ ? `Expected content not to include type "${expectedType}", but it did`
77
+ : `Expected content to include type "${expectedType}". Found types: ${JSON.stringify(types)}`,
78
+ };
79
+ },
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Deep partial match: every key in `expected` must exist in `actual` and match.
85
+ * Extra keys in `actual` are allowed.
86
+ */
87
+ function deepPartialMatch(actual, expected) {
88
+ if (expected === actual) return true;
89
+ if (expected === null || actual === null) return expected === actual;
90
+ if (typeof expected !== 'object' || typeof actual !== 'object') return expected === actual;
91
+ if (Array.isArray(expected)) {
92
+ if (!Array.isArray(actual)) return false;
93
+ if (expected.length !== actual.length) return false;
94
+ return expected.every((item, i) => deepPartialMatch(actual[i], item));
95
+ }
96
+ return Object.keys(expected).every(
97
+ (key) => key in actual && deepPartialMatch(actual[key], expected[key])
98
+ );
99
+ }
@@ -0,0 +1,66 @@
1
+ import type { PlaywrightTestConfig } from '@playwright/test';
2
+
3
+ /**
4
+ * MCP server connection configuration.
5
+ */
6
+ export interface ServerConfig {
7
+ /** Server start command (e.g., 'python'). */
8
+ command?: string;
9
+ /** Command arguments (e.g., ['server.py']). */
10
+ args?: string[];
11
+ /** HTTP server URL (alternative to command/args). */
12
+ url?: string;
13
+ /** Environment variables for the server process. */
14
+ env?: Record<string, string>;
15
+ }
16
+
17
+ /**
18
+ * Visual regression testing configuration.
19
+ *
20
+ * All fields except `snapshotPathTemplate` are forwarded to Playwright's
21
+ * `expect.toHaveScreenshot` config. See Playwright docs for the full set
22
+ * of options (threshold, maxDiffPixelRatio, maxDiffPixels, animations, etc.).
23
+ */
24
+ export interface VisualConfig {
25
+ /** Snapshot directory path template. Default: '{testDir}/__screenshots__/{projectName}/{testFilePath}/{arg}{ext}'. */
26
+ snapshotPathTemplate?: string;
27
+ /** Pixel comparison threshold (0-1). */
28
+ threshold?: number;
29
+ /** Maximum allowed ratio of differing pixels (0-1). */
30
+ maxDiffPixelRatio?: number;
31
+ /** Absolute count of allowed different pixels. */
32
+ maxDiffPixels?: number;
33
+ /** Any other Playwright toHaveScreenshot options applied as project-wide defaults. */
34
+ [key: string]: unknown;
35
+ }
36
+
37
+ /**
38
+ * Configuration options for sunpeak test config.
39
+ */
40
+ export interface TestConfigOptions {
41
+ /**
42
+ * MCP server connection. Omit for sunpeak framework projects (auto-detected).
43
+ * Required for external MCP servers.
44
+ */
45
+ server?: ServerConfig;
46
+ /** Host shells to test against (default: ['chatgpt', 'claude']). */
47
+ hosts?: string[];
48
+ /** Test directory (default: 'tests/e2e' for sunpeak, '.' for external). */
49
+ testDir?: string;
50
+ /** Simulations directory for mock data. */
51
+ simulationsDir?: string;
52
+ /** Global setup file path. */
53
+ globalSetup?: string;
54
+ /** Additional Playwright `use` options. */
55
+ use?: Record<string, unknown>;
56
+ /** Visual regression testing configuration. */
57
+ visual?: VisualConfig;
58
+ }
59
+
60
+ /**
61
+ * Create a Playwright config for testing MCP servers.
62
+ *
63
+ * Auto-detects sunpeak projects and starts `sunpeak dev` as the backend.
64
+ * For external servers, starts `sunpeak inspect` with the provided server config.
65
+ */
66
+ export declare function defineConfig(options?: TestConfigOptions): PlaywrightTestConfig;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Playwright config factory for MCP server testing.
3
+ *
4
+ * Auto-detects project type:
5
+ * - sunpeak framework projects: starts `sunpeak dev` as the backend
6
+ * - External MCP servers: starts `sunpeak inspect` as the backend
7
+ *
8
+ * Usage (sunpeak project):
9
+ * import { defineConfig } from 'sunpeak/test/config';
10
+ * export default defineConfig();
11
+ *
12
+ * Usage (external server):
13
+ * import { defineConfig } from 'sunpeak/test/config';
14
+ * export default defineConfig({
15
+ * server: { command: 'python', args: ['server.py'] },
16
+ * });
17
+ */
18
+ import { existsSync, readFileSync } from 'fs';
19
+ import { join } from 'path';
20
+ import { createBaseConfig, resolvePorts } from './base-config.mjs';
21
+
22
+ /**
23
+ * @param {Object} [options]
24
+ * @param {Object} [options.server] - MCP server connection (omit for sunpeak projects)
25
+ * @param {string} [options.server.command] - Server start command
26
+ * @param {string[]} [options.server.args] - Command arguments
27
+ * @param {string} [options.server.url] - HTTP server URL (alternative to command)
28
+ * @param {Record<string, string>} [options.server.env] - Environment variables
29
+ * @param {string[]} [options.hosts] - Host shells to test (default: ['chatgpt', 'claude'])
30
+ * @param {string} [options.testDir] - Test directory
31
+ * @param {string} [options.simulationsDir] - Simulations directory for mock data
32
+ * @param {string} [options.globalSetup] - Global setup file path
33
+ * @param {Object} [options.use] - Additional Playwright `use` options
34
+ * @returns {import('@playwright/test').PlaywrightTestConfig}
35
+ */
36
+ export function defineConfig(options = {}) {
37
+ const {
38
+ server,
39
+ hosts = ['chatgpt', 'claude'],
40
+ testDir,
41
+ simulationsDir,
42
+ globalSetup,
43
+ use: userUse,
44
+ visual,
45
+ } = options;
46
+
47
+ const { port, sandboxPort } = resolvePorts();
48
+ const isSunpeakProject = !server && detectSunpeakProject();
49
+
50
+ const resolvedTestDir = testDir || (isSunpeakProject ? 'tests/e2e' : '.');
51
+
52
+ let command;
53
+ if (server) {
54
+ // External MCP server mode
55
+ command = buildInspectCommand({ server, port, sandboxPort, simulationsDir });
56
+ } else if (isSunpeakProject) {
57
+ // sunpeak framework project mode
58
+ command = `PORT=${port} SUNPEAK_SANDBOX_PORT=${sandboxPort} pnpm dev`;
59
+ } else {
60
+ throw new Error(
61
+ 'defineConfig: either provide a `server` option or run from a sunpeak project directory.'
62
+ );
63
+ }
64
+
65
+ return createBaseConfig({
66
+ hosts,
67
+ testDir: resolvedTestDir,
68
+ port,
69
+ use: userUse,
70
+ globalSetup,
71
+ visual,
72
+ webServer: {
73
+ command,
74
+ healthUrl: `http://localhost:${port}/health`,
75
+ },
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Detect if the current directory is a sunpeak framework project.
81
+ */
82
+ function detectSunpeakProject() {
83
+ const pkgPath = join(process.cwd(), 'package.json');
84
+ if (!existsSync(pkgPath)) return false;
85
+ try {
86
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
87
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
88
+ return 'sunpeak' in deps;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Build the `sunpeak inspect` command for external MCP servers.
96
+ */
97
+ function buildInspectCommand({ server, port, sandboxPort, simulationsDir }) {
98
+ const parts = [`SUNPEAK_SANDBOX_PORT=${sandboxPort}`];
99
+
100
+ if (server.env) {
101
+ for (const [key, value] of Object.entries(server.env)) {
102
+ parts.push(`${key}=${value}`);
103
+ }
104
+ }
105
+
106
+ parts.push('npx sunpeak inspect');
107
+
108
+ if (server.url) {
109
+ parts.push(`--server ${server.url}`);
110
+ } else if (server.command) {
111
+ const cmd = server.args
112
+ ? `${server.command} ${server.args.join(' ')}`
113
+ : server.command;
114
+ // Quote the command if it contains spaces
115
+ parts.push(`--server "${cmd}"`);
116
+ }
117
+
118
+ if (simulationsDir) {
119
+ parts.push(`--simulations ${simulationsDir}`);
120
+ }
121
+
122
+ parts.push(`--port ${port}`);
123
+
124
+ return parts.join(' ');
125
+ }
@@ -0,0 +1,129 @@
1
+ import type {
2
+ Page,
3
+ FrameLocator,
4
+ Locator,
5
+ TestType,
6
+ Expect,
7
+ PageAssertionsToHaveScreenshotOptions,
8
+ } from '@playwright/test';
9
+
10
+ /**
11
+ * Result from calling an MCP tool via the inspector.
12
+ */
13
+ export interface ToolResult {
14
+ /** Raw MCP content items from the tool response. */
15
+ content: Array<{ type: string; text?: string; [key: string]: unknown }>;
16
+ /** Structured content from the tool response. */
17
+ structuredContent?: unknown;
18
+ /** Whether the tool returned an error. */
19
+ isError: boolean;
20
+ /**
21
+ * Get a FrameLocator for the rendered resource UI.
22
+ * Handles the double-iframe traversal automatically.
23
+ */
24
+ app(): FrameLocator;
25
+ }
26
+
27
+ /**
28
+ * Options for callTool().
29
+ */
30
+ export interface CallToolOptions {
31
+ /** Color theme for the inspector. */
32
+ theme?: 'light' | 'dark';
33
+ /** Display mode for the resource. */
34
+ displayMode?: 'inline' | 'pip' | 'fullscreen';
35
+ /** Use production resource builds instead of HMR. */
36
+ prodResources?: boolean;
37
+ /** Additional inspector URL parameters. */
38
+ [key: string]: unknown;
39
+ }
40
+
41
+ /**
42
+ * Options for screenshot().
43
+ *
44
+ * Extends Playwright's toHaveScreenshot() options with sunpeak-specific
45
+ * `target` and `element` fields. All standard Playwright options (threshold,
46
+ * maxDiffPixelRatio, maxDiffPixels, mask, maskColor, animations, caret,
47
+ * fullPage, clip, scale, stylePath, omitBackground, timeout, etc.)
48
+ * are passed through directly.
49
+ */
50
+ export interface ScreenshotOptions extends PageAssertionsToHaveScreenshotOptions {
51
+ /** What to screenshot: 'app' (inner iframe content) or 'page' (full inspector). Default: 'app'. */
52
+ target?: 'app' | 'page';
53
+ /** Specific locator to screenshot instead of the default target. */
54
+ element?: Locator;
55
+ }
56
+
57
+ /**
58
+ * MCP test fixture for testing MCP servers via the inspector.
59
+ */
60
+ export interface McpFixture {
61
+ /** The underlying Playwright Page. */
62
+ page: Page;
63
+ /** Current host ID (from Playwright project name). */
64
+ host: string;
65
+
66
+ /**
67
+ * Call a tool and get the rendered result.
68
+ * Navigates the inspector, waits for the resource to render,
69
+ * and returns a ToolResult for assertions.
70
+ */
71
+ callTool(
72
+ name: string,
73
+ input?: Record<string, unknown>,
74
+ options?: CallToolOptions
75
+ ): Promise<ToolResult>;
76
+
77
+ /**
78
+ * Navigate to a tool with no mock data ("Press Run" state).
79
+ */
80
+ openTool(name: string, options?: { theme?: 'light' | 'dark' }): Promise<void>;
81
+
82
+ /**
83
+ * Click the Run button and return the rendered result.
84
+ */
85
+ runTool(): Promise<ToolResult>;
86
+
87
+ /** Change the theme via the sidebar toggle. */
88
+ setTheme(theme: 'light' | 'dark'): Promise<void>;
89
+
90
+ /** Change the display mode via the sidebar buttons. */
91
+ setDisplayMode(mode: 'inline' | 'pip' | 'fullscreen'): Promise<void>;
92
+
93
+ /**
94
+ * Take a screenshot and compare against a baseline.
95
+ * Only performs the comparison when visual testing is enabled
96
+ * (`sunpeak test --visual`). Silently skips otherwise.
97
+ *
98
+ * @param name - Snapshot name (auto-generated from test title if omitted)
99
+ * @param options - Screenshot and comparison options
100
+ */
101
+ screenshot(name?: string, options?: ScreenshotOptions): Promise<void>;
102
+ }
103
+
104
+ /**
105
+ * Extended Playwright test with `mcp` fixture.
106
+ */
107
+ export declare const test: TestType<{ mcp: McpFixture }, {}>;
108
+
109
+ /**
110
+ * Extended Playwright expect with MCP-native matchers.
111
+ */
112
+ export declare const expect: Expect<{
113
+ /**
114
+ * Assert that a tool result is an error.
115
+ */
116
+ toBeError(): void;
117
+ /**
118
+ * Assert that any content item's text contains the given string.
119
+ */
120
+ toHaveTextContent(text: string): void;
121
+ /**
122
+ * Assert that structuredContent matches the expected shape.
123
+ */
124
+ toHaveStructuredContent(shape: unknown): void;
125
+ /**
126
+ * Assert that content array contains an item of the given type.
127
+ */
128
+ toHaveContentType(type: string): void;
129
+ }>;