Skip to content

ブラウザとプレビュー

Traeの統合ブラウザとプレビュー機能を使用して、開発ワークフローを効率化しましょう。

概要

Traeの統合ブラウザとプレビュー機能により、IDE内で直接Webアプリケーションを表示、テスト、デバッグできます:

  • 統合ブラウザ: IDE内でのWebページ表示
  • ライブプレビュー: リアルタイムでの変更反映
  • レスポンシブテスト: 複数デバイスサイズでのテスト
  • デバッグツール: 統合された開発者ツール
  • パフォーマンス監視: リアルタイムでのパフォーマンス分析

統合ブラウザの使用

ブラウザパネルの開き方

  1. サイドバーから:

    サイドバー > ブラウザアイコンをクリック
  2. コマンドパレットから:

    Ctrl+Shift+P (Windows/Linux)
    Cmd+Shift+P (macOS)
    > "ブラウザを開く"を検索
  3. メニューから:

    表示 > ブラウザ

基本的な操作

typescript
// ブラウザコントロールAPI
class BrowserController {
  constructor() {
    this.currentUrl = '';
    this.history = [];
    this.bookmarks = [];
  }

  // URLに移動
  navigateTo(url: string) {
    this.history.push(this.currentUrl);
    this.currentUrl = url;
    this.loadPage(url);
  }

  // ページを再読み込み
  refresh() {
    this.loadPage(this.currentUrl);
  }

  // 戻る
  goBack() {
    if (this.history.length > 0) {
      const previousUrl = this.history.pop();
      this.currentUrl = previousUrl;
      this.loadPage(previousUrl);
    }
  }

  // 進む
  goForward() {
    // 進む履歴の実装
  }

  // ブックマークを追加
  addBookmark(url: string, title: string) {
    this.bookmarks.push({ url, title, date: new Date() });
  }

  private loadPage(url: string) {
    // ページ読み込みの実装
    console.log(`Loading: ${url}`);
  }
}

// 使用例
const browser = new BrowserController();
browser.navigateTo('http://localhost:3000');

ライブプレビュー

自動プレビューの設定

typescript
// ライブプレビュー設定
interface PreviewConfig {
  autoRefresh: boolean;
  refreshDelay: number; // ミリ秒
  watchFiles: string[];
  excludeFiles: string[];
  port: number;
  host: string;
}

const previewConfig: PreviewConfig = {
  autoRefresh: true,
  refreshDelay: 500,
  watchFiles: ['**/*.html', '**/*.css', '**/*.js', '**/*.ts'],
  excludeFiles: ['node_modules/**', 'dist/**'],
  port: 3000,
  host: 'localhost'
};

// ライブプレビューサーバーの開始
class LivePreviewServer {
  private config: PreviewConfig;
  private watchers: Map<string, any> = new Map();

  constructor(config: PreviewConfig) {
    this.config = config;
  }

  start() {
    // サーバーを開始
    this.startServer();
    
    // ファイル監視を開始
    this.startFileWatching();
    
    console.log(`Live preview server started on http://${this.config.host}:${this.config.port}`);
  }

  private startServer() {
    // Express サーバーの設定
    const express = require('express');
    const app = express();
    
    app.use(express.static('.'));
    
    app.listen(this.config.port, this.config.host, () => {
      console.log(`Server running on port ${this.config.port}`);
    });
  }

  private startFileWatching() {
    const chokidar = require('chokidar');
    
    const watcher = chokidar.watch(this.config.watchFiles, {
      ignored: this.config.excludeFiles,
      persistent: true
    });

    watcher.on('change', (path: string) => {
      setTimeout(() => {
        this.notifyBrowserRefresh();
      }, this.config.refreshDelay);
    });
  }

  private notifyBrowserRefresh() {
    // WebSocketを使用してブラウザに更新を通知
    this.broadcastRefresh();
  }

  private broadcastRefresh() {
    // WebSocket実装
    console.log('Broadcasting refresh to connected browsers');
  }
}

ホットリロードの実装

typescript
// ホットリロード機能
class HotReload {
  private websocket: WebSocket;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;

  constructor(port: number = 3001) {
    this.connect(port);
  }

  private connect(port: number) {
    try {
      this.websocket = new WebSocket(`ws://localhost:${port}`);
      
      this.websocket.onopen = () => {
        console.log('Hot reload connected');
        this.reconnectAttempts = 0;
      };

      this.websocket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.handleMessage(data);
      };

      this.websocket.onclose = () => {
        console.log('Hot reload disconnected');
        this.attemptReconnect(port);
      };

      this.websocket.onerror = (error) => {
        console.error('Hot reload error:', error);
      };
    } catch (error) {
      console.error('Failed to connect to hot reload server:', error);
    }
  }

  private handleMessage(data: any) {
    switch (data.type) {
      case 'reload':
        window.location.reload();
        break;
      
      case 'css-update':
        this.updateCSS(data.path);
        break;
      
      case 'js-update':
        this.updateJavaScript(data.path);
        break;
      
      default:
        console.log('Unknown hot reload message:', data);
    }
  }

  private updateCSS(path: string) {
    const links = document.querySelectorAll(`link[href*="${path}"]`);
    links.forEach(link => {
      const href = link.getAttribute('href');
      const newHref = href + '?t=' + Date.now();
      link.setAttribute('href', newHref);
    });
  }

  private updateJavaScript(path: string) {
    // JavaScriptのホットリロードは複雑なため、通常はページ全体を再読み込み
    window.location.reload();
  }

  private attemptReconnect(port: number) {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      setTimeout(() => {
        console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
        this.connect(port);
      }, 2000 * this.reconnectAttempts);
    }
  }
}

// ページに自動的にホットリロードを注入
if (process.env.NODE_ENV === 'development') {
  new HotReload();
}

レスポンシブテスト

デバイスプリセット

typescript
// デバイスプリセット定義
interface DevicePreset {
  name: string;
  width: number;
  height: number;
  userAgent: string;
  pixelRatio: number;
  touch: boolean;
}

const devicePresets: DevicePreset[] = [
  {
    name: 'iPhone 14 Pro',
    width: 393,
    height: 852,
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15',
    pixelRatio: 3,
    touch: true
  },
  {
    name: 'iPad Pro',
    width: 1024,
    height: 1366,
    userAgent: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15',
    pixelRatio: 2,
    touch: true
  },
  {
    name: 'Samsung Galaxy S23',
    width: 360,
    height: 780,
    userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S911B) AppleWebKit/537.36',
    pixelRatio: 3,
    touch: true
  },
  {
    name: 'Desktop 1920x1080',
    width: 1920,
    height: 1080,
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    pixelRatio: 1,
    touch: false
  }
];

// レスポンシブテストコントローラー
class ResponsiveTestController {
  private currentDevice: DevicePreset;
  private viewport: HTMLElement;

  constructor(viewportElement: HTMLElement) {
    this.viewport = viewportElement;
    this.currentDevice = devicePresets[0];
  }

  setDevice(deviceName: string) {
    const device = devicePresets.find(d => d.name === deviceName);
    if (device) {
      this.currentDevice = device;
      this.applyDeviceSettings();
    }
  }

  setCustomSize(width: number, height: number) {
    this.currentDevice = {
      name: 'Custom',
      width,
      height,
      userAgent: navigator.userAgent,
      pixelRatio: window.devicePixelRatio,
      touch: 'ontouchstart' in window
    };
    this.applyDeviceSettings();
  }

  private applyDeviceSettings() {
    // ビューポートサイズを設定
    this.viewport.style.width = `${this.currentDevice.width}px`;
    this.viewport.style.height = `${this.currentDevice.height}px`;
    
    // ユーザーエージェントを設定(可能な場合)
    if ('userAgent' in navigator) {
      Object.defineProperty(navigator, 'userAgent', {
        value: this.currentDevice.userAgent,
        configurable: true
      });
    }
    
    // デバイス情報を表示
    this.displayDeviceInfo();
  }

  private displayDeviceInfo() {
    const info = document.getElementById('device-info');
    if (info) {
      info.innerHTML = `
        <div class="device-info">
          <h3>${this.currentDevice.name}</h3>
          <p>サイズ: ${this.currentDevice.width} × ${this.currentDevice.height}</p>
          <p>ピクセル比: ${this.currentDevice.pixelRatio}</p>
          <p>タッチ: ${this.currentDevice.touch ? 'あり' : 'なし'}</p>
        </div>
      `;
    }
  }

  // スクリーンショットを撮影
  async takeScreenshot(): Promise<string> {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    canvas.width = this.currentDevice.width;
    canvas.height = this.currentDevice.height;
    
    // html2canvasライブラリを使用
    const html2canvas = (await import('html2canvas')).default;
    const screenshot = await html2canvas(this.viewport);
    
    return screenshot.toDataURL('image/png');
  }
}

ブレークポイントテスト

typescript
// ブレークポイント定義
interface Breakpoint {
  name: string;
  minWidth: number;
  maxWidth?: number;
  description: string;
}

const breakpoints: Breakpoint[] = [
  { name: 'xs', minWidth: 0, maxWidth: 575, description: 'Extra Small' },
  { name: 'sm', minWidth: 576, maxWidth: 767, description: 'Small' },
  { name: 'md', minWidth: 768, maxWidth: 991, description: 'Medium' },
  { name: 'lg', minWidth: 992, maxWidth: 1199, description: 'Large' },
  { name: 'xl', minWidth: 1200, maxWidth: 1399, description: 'Extra Large' },
  { name: 'xxl', minWidth: 1400, description: 'Extra Extra Large' }
];

// ブレークポイントテスター
class BreakpointTester {
  private viewport: HTMLElement;
  private currentBreakpoint: Breakpoint;

  constructor(viewportElement: HTMLElement) {
    this.viewport = viewportElement;
    this.updateBreakpoint();
    this.setupResizeListener();
  }

  private setupResizeListener() {
    const resizeObserver = new ResizeObserver(() => {
      this.updateBreakpoint();
    });
    
    resizeObserver.observe(this.viewport);
  }

  private updateBreakpoint() {
    const width = this.viewport.offsetWidth;
    const breakpoint = this.getBreakpointForWidth(width);
    
    if (breakpoint !== this.currentBreakpoint) {
      this.currentBreakpoint = breakpoint;
      this.onBreakpointChange(breakpoint);
    }
  }

  private getBreakpointForWidth(width: number): Breakpoint {
    return breakpoints.find(bp => 
      width >= bp.minWidth && (bp.maxWidth === undefined || width <= bp.maxWidth)
    ) || breakpoints[breakpoints.length - 1];
  }

  private onBreakpointChange(breakpoint: Breakpoint) {
    // ブレークポイント変更イベントを発火
    const event = new CustomEvent('breakpointChange', {
      detail: { breakpoint, width: this.viewport.offsetWidth }
    });
    
    this.viewport.dispatchEvent(event);
    
    // UI更新
    this.updateBreakpointIndicator(breakpoint);
  }

  private updateBreakpointIndicator(breakpoint: Breakpoint) {
    const indicator = document.getElementById('breakpoint-indicator');
    if (indicator) {
      indicator.innerHTML = `
        <div class="breakpoint-indicator">
          <span class="breakpoint-name">${breakpoint.name}</span>
          <span class="breakpoint-description">${breakpoint.description}</span>
          <span class="breakpoint-width">${this.viewport.offsetWidth}px</span>
        </div>
      `;
    }
  }

  // 全ブレークポイントでテスト
  async testAllBreakpoints(): Promise<BreakpointTestResult[]> {
    const results: BreakpointTestResult[] = [];
    
    for (const breakpoint of breakpoints) {
      const testWidth = breakpoint.minWidth + 50; // 各ブレークポイントの代表的な幅
      
      // ビューポートサイズを設定
      this.viewport.style.width = `${testWidth}px`;
      
      // レンダリングを待つ
      await new Promise(resolve => setTimeout(resolve, 100));
      
      // スクリーンショットを撮影
      const screenshot = await this.takeScreenshot();
      
      results.push({
        breakpoint,
        width: testWidth,
        screenshot,
        timestamp: new Date()
      });
    }
    
    return results;
  }

  private async takeScreenshot(): Promise<string> {
    // スクリーンショット実装
    return 'data:image/png;base64,...';
  }
}

interface BreakpointTestResult {
  breakpoint: Breakpoint;
  width: number;
  screenshot: string;
  timestamp: Date;
}

開発者ツール

統合デバッガー

typescript
// 統合開発者ツール
class IntegratedDevTools {
  private consolePanel: HTMLElement;
  private networkPanel: HTMLElement;
  private elementsPanel: HTMLElement;

  constructor() {
    this.initializePanels();
    this.setupConsoleCapture();
    this.setupNetworkMonitoring();
  }

  private initializePanels() {
    // コンソールパネル
    this.consolePanel = this.createPanel('console');
    
    // ネットワークパネル
    this.networkPanel = this.createPanel('network');
    
    // 要素パネル
    this.elementsPanel = this.createPanel('elements');
  }

  private createPanel(type: string): HTMLElement {
    const panel = document.createElement('div');
    panel.className = `devtools-panel devtools-${type}`;
    panel.id = `devtools-${type}`;
    
    return panel;
  }

  private setupConsoleCapture() {
    // コンソールメッセージをキャプチャ
    const originalLog = console.log;
    const originalError = console.error;
    const originalWarn = console.warn;

    console.log = (...args) => {
      originalLog.apply(console, args);
      this.addConsoleMessage('log', args);
    };

    console.error = (...args) => {
      originalError.apply(console, args);
      this.addConsoleMessage('error', args);
    };

    console.warn = (...args) => {
      originalWarn.apply(console, args);
      this.addConsoleMessage('warn', args);
    };
  }

  private addConsoleMessage(type: string, args: any[]) {
    const message = document.createElement('div');
    message.className = `console-message console-${type}`;
    message.innerHTML = `
      <span class="console-timestamp">${new Date().toLocaleTimeString()}</span>
      <span class="console-type">${type.toUpperCase()}</span>
      <span class="console-content">${args.join(' ')}</span>
    `;
    
    this.consolePanel.appendChild(message);
    this.consolePanel.scrollTop = this.consolePanel.scrollHeight;
  }

  private setupNetworkMonitoring() {
    // XMLHttpRequestを監視
    const originalXHR = window.XMLHttpRequest;
    const self = this;

    window.XMLHttpRequest = function() {
      const xhr = new originalXHR();
      const startTime = Date.now();

      xhr.addEventListener('loadstart', () => {
        self.addNetworkRequest({
          method: 'GET', // 実際のメソッドを取得する必要がある
          url: xhr.responseURL || 'Unknown',
          status: 'pending',
          startTime
        });
      });

      xhr.addEventListener('loadend', () => {
        const endTime = Date.now();
        self.updateNetworkRequest({
          url: xhr.responseURL,
          status: xhr.status,
          duration: endTime - startTime,
          size: xhr.responseText?.length || 0
        });
      });

      return xhr;
    };

    // Fetch APIを監視
    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
      const startTime = Date.now();
      const url = args[0] as string;
      
      self.addNetworkRequest({
        method: 'GET',
        url,
        status: 'pending',
        startTime
      });

      try {
        const response = await originalFetch.apply(this, args);
        const endTime = Date.now();
        
        self.updateNetworkRequest({
          url,
          status: response.status,
          duration: endTime - startTime,
          size: parseInt(response.headers.get('content-length') || '0')
        });

        return response;
      } catch (error) {
        const endTime = Date.now();
        
        self.updateNetworkRequest({
          url,
          status: 'error',
          duration: endTime - startTime,
          size: 0
        });

        throw error;
      }
    };
  }

  private addNetworkRequest(request: any) {
    const row = document.createElement('div');
    row.className = 'network-request';
    row.innerHTML = `
      <span class="network-method">${request.method}</span>
      <span class="network-url">${request.url}</span>
      <span class="network-status">${request.status}</span>
      <span class="network-duration">-</span>
      <span class="network-size">-</span>
    `;
    
    this.networkPanel.appendChild(row);
  }

  private updateNetworkRequest(update: any) {
    // ネットワークリクエストの更新実装
    const rows = this.networkPanel.querySelectorAll('.network-request');
    // 対応する行を見つけて更新
  }
}

パフォーマンス監視

typescript
// パフォーマンス監視
class PerformanceMonitor {
  private metrics: PerformanceMetrics = {
    loadTime: 0,
    domContentLoaded: 0,
    firstPaint: 0,
    firstContentfulPaint: 0,
    largestContentfulPaint: 0,
    cumulativeLayoutShift: 0,
    firstInputDelay: 0
  };

  constructor() {
    this.startMonitoring();
  }

  private startMonitoring() {
    // ページロード時間
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
      this.metrics.loadTime = navigation.loadEventEnd - navigation.loadEventStart;
      this.metrics.domContentLoaded = navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart;
    });

    // Core Web Vitals
    this.observeWebVitals();
    
    // リアルタイム更新
    this.startRealtimeUpdates();
  }

  private observeWebVitals() {
    // Largest Contentful Paint
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      const lastEntry = entries[entries.length - 1];
      this.metrics.largestContentfulPaint = lastEntry.startTime;
      this.updateDisplay();
    }).observe({ entryTypes: ['largest-contentful-paint'] });

    // First Input Delay
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      entries.forEach((entry: any) => {
        this.metrics.firstInputDelay = entry.processingStart - entry.startTime;
        this.updateDisplay();
      });
    }).observe({ entryTypes: ['first-input'] });

    // Cumulative Layout Shift
    new PerformanceObserver((list) => {
      let clsValue = 0;
      const entries = list.getEntries();
      
      entries.forEach((entry: any) => {
        if (!entry.hadRecentInput) {
          clsValue += entry.value;
        }
      });
      
      this.metrics.cumulativeLayoutShift = clsValue;
      this.updateDisplay();
    }).observe({ entryTypes: ['layout-shift'] });
  }

  private startRealtimeUpdates() {
    setInterval(() => {
      this.updateMemoryUsage();
      this.updateFPS();
      this.updateDisplay();
    }, 1000);
  }

  private updateMemoryUsage() {
    if ('memory' in performance) {
      const memory = (performance as any).memory;
      this.metrics.memoryUsage = {
        used: memory.usedJSHeapSize,
        total: memory.totalJSHeapSize,
        limit: memory.jsHeapSizeLimit
      };
    }
  }

  private updateFPS() {
    let lastTime = performance.now();
    let frames = 0;

    const countFPS = () => {
      frames++;
      const currentTime = performance.now();
      
      if (currentTime >= lastTime + 1000) {
        this.metrics.fps = Math.round((frames * 1000) / (currentTime - lastTime));
        frames = 0;
        lastTime = currentTime;
      }
      
      requestAnimationFrame(countFPS);
    };

    requestAnimationFrame(countFPS);
  }

  private updateDisplay() {
    const display = document.getElementById('performance-metrics');
    if (display) {
      display.innerHTML = this.generateMetricsHTML();
    }
  }

  private generateMetricsHTML(): string {
    return `
      <div class="performance-metrics">
        <h3>パフォーマンス指標</h3>
        
        <div class="metric-group">
          <h4>Core Web Vitals</h4>
          <div class="metric">
            <span class="metric-label">LCP:</span>
            <span class="metric-value ${this.getLCPClass()}">${this.metrics.largestContentfulPaint.toFixed(2)}ms</span>
          </div>
          <div class="metric">
            <span class="metric-label">FID:</span>
            <span class="metric-value ${this.getFIDClass()}">${this.metrics.firstInputDelay.toFixed(2)}ms</span>
          </div>
          <div class="metric">
            <span class="metric-label">CLS:</span>
            <span class="metric-value ${this.getCLSClass()}">${this.metrics.cumulativeLayoutShift.toFixed(3)}</span>
          </div>
        </div>

        <div class="metric-group">
          <h4>ロード時間</h4>
          <div class="metric">
            <span class="metric-label">ページロード:</span>
            <span class="metric-value">${this.metrics.loadTime.toFixed(2)}ms</span>
          </div>
          <div class="metric">
            <span class="metric-label">DOM準備完了:</span>
            <span class="metric-value">${this.metrics.domContentLoaded.toFixed(2)}ms</span>
          </div>
        </div>

        <div class="metric-group">
          <h4>リアルタイム</h4>
          <div class="metric">
            <span class="metric-label">FPS:</span>
            <span class="metric-value">${this.metrics.fps || 0}</span>
          </div>
          ${this.metrics.memoryUsage ? `
            <div class="metric">
              <span class="metric-label">メモリ使用量:</span>
              <span class="metric-value">${(this.metrics.memoryUsage.used / 1024 / 1024).toFixed(2)} MB</span>
            </div>
          ` : ''}
        </div>
      </div>
    `;
  }

  private getLCPClass(): string {
    if (this.metrics.largestContentfulPaint <= 2500) return 'good';
    if (this.metrics.largestContentfulPaint <= 4000) return 'needs-improvement';
    return 'poor';
  }

  private getFIDClass(): string {
    if (this.metrics.firstInputDelay <= 100) return 'good';
    if (this.metrics.firstInputDelay <= 300) return 'needs-improvement';
    return 'poor';
  }

  private getCLSClass(): string {
    if (this.metrics.cumulativeLayoutShift <= 0.1) return 'good';
    if (this.metrics.cumulativeLayoutShift <= 0.25) return 'needs-improvement';
    return 'poor';
  }
}

interface PerformanceMetrics {
  loadTime: number;
  domContentLoaded: number;
  firstPaint: number;
  firstContentfulPaint: number;
  largestContentfulPaint: number;
  cumulativeLayoutShift: number;
  firstInputDelay: number;
  fps?: number;
  memoryUsage?: {
    used: number;
    total: number;
    limit: number;
  };
}

高度な機能

マルチブラウザテスト

typescript
// マルチブラウザテストマネージャー
class MultiBrowserTestManager {
  private browsers: BrowserInstance[] = [];
  private testSuites: TestSuite[] = [];

  constructor() {
    this.initializeBrowsers();
  }

  private initializeBrowsers() {
    const browserConfigs = [
      { name: 'Chrome', engine: 'Chromium', version: 'latest' },
      { name: 'Firefox', engine: 'Gecko', version: 'latest' },
      { name: 'Safari', engine: 'WebKit', version: 'latest' },
      { name: 'Edge', engine: 'Chromium', version: 'latest' }
    ];

    browserConfigs.forEach(config => {
      this.browsers.push(new BrowserInstance(config));
    });
  }

  async runTestSuite(url: string): Promise<TestResults> {
    const results: TestResults = {
      url,
      timestamp: new Date(),
      browserResults: []
    };

    for (const browser of this.browsers) {
      try {
        const browserResult = await this.runTestInBrowser(browser, url);
        results.browserResults.push(browserResult);
      } catch (error) {
        results.browserResults.push({
          browser: browser.name,
          success: false,
          error: error.message,
          screenshots: [],
          performanceMetrics: null
        });
      }
    }

    return results;
  }

  private async runTestInBrowser(browser: BrowserInstance, url: string): Promise<BrowserTestResult> {
    await browser.navigate(url);
    
    // スクリーンショットを撮影
    const screenshots = await this.takeScreenshots(browser);
    
    // パフォーマンス指標を収集
    const performanceMetrics = await browser.getPerformanceMetrics();
    
    // 機能テストを実行
    const functionalTests = await this.runFunctionalTests(browser);
    
    return {
      browser: browser.name,
      success: true,
      screenshots,
      performanceMetrics,
      functionalTests,
      error: null
    };
  }

  private async takeScreenshots(browser: BrowserInstance): Promise<Screenshot[]> {
    const screenshots: Screenshot[] = [];
    
    // デスクトップビュー
    await browser.setViewport(1920, 1080);
    screenshots.push({
      name: 'desktop',
      data: await browser.screenshot(),
      viewport: { width: 1920, height: 1080 }
    });
    
    // タブレットビュー
    await browser.setViewport(768, 1024);
    screenshots.push({
      name: 'tablet',
      data: await browser.screenshot(),
      viewport: { width: 768, height: 1024 }
    });
    
    // モバイルビュー
    await browser.setViewport(375, 667);
    screenshots.push({
      name: 'mobile',
      data: await browser.screenshot(),
      viewport: { width: 375, height: 667 }
    });
    
    return screenshots;
  }

  private async runFunctionalTests(browser: BrowserInstance): Promise<FunctionalTestResult[]> {
    const tests: FunctionalTestResult[] = [];
    
    // リンクテスト
    const links = await browser.findElements('a[href]');
    for (const link of links) {
      const href = await link.getAttribute('href');
      const isWorking = await this.testLink(browser, href);
      tests.push({
        type: 'link',
        element: href,
        success: isWorking,
        message: isWorking ? 'Link is working' : 'Link is broken'
      });
    }
    
    // フォームテスト
    const forms = await browser.findElements('form');
    for (const form of forms) {
      const isWorking = await this.testForm(browser, form);
      tests.push({
        type: 'form',
        element: 'form',
        success: isWorking,
        message: isWorking ? 'Form is working' : 'Form has issues'
      });
    }
    
    return tests;
  }

  private async testLink(browser: BrowserInstance, href: string): Promise<boolean> {
    try {
      const response = await fetch(href, { method: 'HEAD' });
      return response.ok;
    } catch {
      return false;
    }
  }

  private async testForm(browser: BrowserInstance, form: any): Promise<boolean> {
    // フォームの基本的な検証
    try {
      const inputs = await form.findElements('input, textarea, select');
      return inputs.length > 0;
    } catch {
      return false;
    }
  }
}

// ブラウザインスタンス
class BrowserInstance {
  name: string;
  engine: string;
  version: string;

  constructor(config: any) {
    this.name = config.name;
    this.engine = config.engine;
    this.version = config.version;
  }

  async navigate(url: string): Promise<void> {
    // ブラウザでURLに移動
  }

  async setViewport(width: number, height: number): Promise<void> {
    // ビューポートサイズを設定
  }

  async screenshot(): Promise<string> {
    // スクリーンショットを撮影
    return 'data:image/png;base64,...';
  }

  async getPerformanceMetrics(): Promise<any> {
    // パフォーマンス指標を取得
    return {};
  }

  async findElements(selector: string): Promise<any[]> {
    // 要素を検索
    return [];
  }
}

interface TestResults {
  url: string;
  timestamp: Date;
  browserResults: BrowserTestResult[];
}

interface BrowserTestResult {
  browser: string;
  success: boolean;
  screenshots: Screenshot[];
  performanceMetrics: any;
  functionalTests?: FunctionalTestResult[];
  error: string | null;
}

interface Screenshot {
  name: string;
  data: string;
  viewport: { width: number; height: number };
}

interface FunctionalTestResult {
  type: string;
  element: string;
  success: boolean;
  message: string;
}

A/Bテスト統合

typescript
// A/Bテスト機能
class ABTestManager {
  private tests: ABTest[] = [];
  private userSegments: UserSegment[] = [];

  constructor() {
    this.loadTests();
    this.initializeTracking();
  }

  createTest(config: ABTestConfig): ABTest {
    const test = new ABTest(config);
    this.tests.push(test);
    return test;
  }

  async runTest(testId: string, userId: string): Promise<ABTestVariant> {
    const test = this.tests.find(t => t.id === testId);
    if (!test) {
      throw new Error(`Test ${testId} not found`);
    }

    const userSegment = this.getUserSegment(userId);
    const variant = test.getVariantForUser(userId, userSegment);
    
    // トラッキング
    this.trackTestExposure(testId, userId, variant.name);
    
    return variant;
  }

  trackConversion(testId: string, userId: string, conversionType: string, value?: number): void {
    const test = this.tests.find(t => t.id === testId);
    if (test) {
      test.trackConversion(userId, conversionType, value);
    }
  }

  getTestResults(testId: string): ABTestResults {
    const test = this.tests.find(t => t.id === testId);
    if (!test) {
      throw new Error(`Test ${testId} not found`);
    }

    return test.getResults();
  }

  private loadTests(): void {
    // 設定からテストを読み込み
  }

  private initializeTracking(): void {
    // トラッキングシステムを初期化
  }

  private getUserSegment(userId: string): UserSegment {
    // ユーザーセグメントを決定
    return this.userSegments[0] || { name: 'default', criteria: {} };
  }

  private trackTestExposure(testId: string, userId: string, variant: string): void {
    // テスト露出をトラッキング
    console.log(`User ${userId} exposed to test ${testId}, variant ${variant}`);
  }
}

class ABTest {
  id: string;
  name: string;
  variants: ABTestVariant[];
  trafficAllocation: number;
  startDate: Date;
  endDate: Date;
  conversions: Map<string, ConversionData[]> = new Map();

  constructor(config: ABTestConfig) {
    this.id = config.id;
    this.name = config.name;
    this.variants = config.variants;
    this.trafficAllocation = config.trafficAllocation;
    this.startDate = config.startDate;
    this.endDate = config.endDate;
  }

  getVariantForUser(userId: string, segment: UserSegment): ABTestVariant {
    // ユーザーハッシュに基づいてバリアントを決定
    const hash = this.hashUserId(userId);
    const variantIndex = hash % this.variants.length;
    return this.variants[variantIndex];
  }

  trackConversion(userId: string, conversionType: string, value?: number): void {
    if (!this.conversions.has(userId)) {
      this.conversions.set(userId, []);
    }

    this.conversions.get(userId)!.push({
      type: conversionType,
      value: value || 1,
      timestamp: new Date()
    });
  }

  getResults(): ABTestResults {
    const results: ABTestResults = {
      testId: this.id,
      variants: [],
      statisticalSignificance: false,
      confidenceLevel: 0
    };

    this.variants.forEach(variant => {
      const variantResults = this.calculateVariantResults(variant);
      results.variants.push(variantResults);
    });

    // 統計的有意性を計算
    results.statisticalSignificance = this.calculateStatisticalSignificance(results.variants);
    results.confidenceLevel = this.calculateConfidenceLevel(results.variants);

    return results;
  }

  private hashUserId(userId: string): number {
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      const char = userId.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // 32bit整数に変換
    }
    return Math.abs(hash);
  }

  private calculateVariantResults(variant: ABTestVariant): VariantResults {
    // バリアント結果を計算
    return {
      variantName: variant.name,
      exposures: 0,
      conversions: 0,
      conversionRate: 0,
      averageValue: 0
    };
  }

  private calculateStatisticalSignificance(variants: VariantResults[]): boolean {
    // 統計的有意性を計算
    return false;
  }

  private calculateConfidenceLevel(variants: VariantResults[]): number {
    // 信頼度を計算
    return 0;
  }
}

interface ABTestConfig {
  id: string;
  name: string;
  variants: ABTestVariant[];
  trafficAllocation: number;
  startDate: Date;
  endDate: Date;
}

interface ABTestVariant {
  name: string;
  weight: number;
  config: any;
}

interface UserSegment {
  name: string;
  criteria: any;
}

interface ConversionData {
  type: string;
  value: number;
  timestamp: Date;
}

interface ABTestResults {
  testId: string;
  variants: VariantResults[];
  statisticalSignificance: boolean;
  confidenceLevel: number;
}

interface VariantResults {
  variantName: string;
  exposures: number;
  conversions: number;
  conversionRate: number;
  averageValue: number;
}

設定とカスタマイズ

ブラウザ設定

typescript
// ブラウザ設定管理
class BrowserSettings {
  private settings: BrowserConfig;

  constructor() {
    this.settings = this.loadSettings();
  }

  private loadSettings(): BrowserConfig {
    const defaultSettings: BrowserConfig = {
      defaultUrl: 'http://localhost:3000',
      autoRefresh: true,
      refreshDelay: 500,
      showDevTools: true,
      enableNetworkThrottling: false,
      networkThrottling: {
        downloadSpeed: 1000, // KB/s
        uploadSpeed: 500,    // KB/s
        latency: 100         // ms
      },
      userAgent: 'Trae Browser/1.0',
      viewport: {
        width: 1920,
        height: 1080
      },
      security: {
        allowMixedContent: false,
        enableCORS: true,
        blockPopups: true
      },
      performance: {
        enableCaching: true,
        maxCacheSize: 100, // MB
        enableCompression: true
      }
    };

    // 保存された設定を読み込み
    const savedSettings = localStorage.getItem('trae-browser-settings');
    if (savedSettings) {
      return { ...defaultSettings, ...JSON.parse(savedSettings) };
    }

    return defaultSettings;
  }

  updateSetting<K extends keyof BrowserConfig>(key: K, value: BrowserConfig[K]): void {
    this.settings[key] = value;
    this.saveSettings();
    this.applySettings();
  }

  private saveSettings(): void {
    localStorage.setItem('trae-browser-settings', JSON.stringify(this.settings));
  }

  private applySettings(): void {
    // 設定を適用
    this.applyViewportSettings();
    this.applyNetworkSettings();
    this.applySecuritySettings();
    this.applyPerformanceSettings();
  }

  private applyViewportSettings(): void {
    const viewport = document.getElementById('browser-viewport');
    if (viewport) {
      viewport.style.width = `${this.settings.viewport.width}px`;
      viewport.style.height = `${this.settings.viewport.height}px`;
    }
  }

  private applyNetworkSettings(): void {
    if (this.settings.enableNetworkThrottling) {
      // ネットワークスロットリングを適用
      this.enableNetworkThrottling(this.settings.networkThrottling);
    }
  }

  private applySecuritySettings(): void {
    // セキュリティ設定を適用
    if (this.settings.security.blockPopups) {
      this.blockPopups();
    }
  }

  private applyPerformanceSettings(): void {
    // パフォーマンス設定を適用
    if (this.settings.performance.enableCaching) {
      this.enableCaching();
    }
  }

  private enableNetworkThrottling(config: NetworkThrottlingConfig): void {
    // ネットワークスロットリングの実装
  }

  private blockPopups(): void {
    // ポップアップブロックの実装
  }

  private enableCaching(): void {
    // キャッシュの有効化
  }

  getSettings(): BrowserConfig {
    return { ...this.settings };
  }
}

interface BrowserConfig {
  defaultUrl: string;
  autoRefresh: boolean;
  refreshDelay: number;
  showDevTools: boolean;
  enableNetworkThrottling: boolean;
  networkThrottling: NetworkThrottlingConfig;
  userAgent: string;
  viewport: ViewportConfig;
  security: SecurityConfig;
  performance: PerformanceConfig;
}

interface NetworkThrottlingConfig {
  downloadSpeed: number;
  uploadSpeed: number;
  latency: number;
}

interface ViewportConfig {
  width: number;
  height: number;
}

interface SecurityConfig {
  allowMixedContent: boolean;
  enableCORS: boolean;
  blockPopups: boolean;
}

interface PerformanceConfig {
  enableCaching: boolean;
  maxCacheSize: number;
  enableCompression: boolean;
}

まとめ

Traeの統合ブラウザとプレビュー機能は:

  • 効率的: IDE内での完結した開発体験
  • 包括的: レスポンシブテストから性能監視まで
  • カスタマイズ可能: 個人の開発スタイルに合わせた設定
  • 高度: A/Bテストやマルチブラウザテストまで対応

これらの機能を活用して、より効率的で品質の高いWeb開発を実現しましょう。

関連記事

これらの機能については、今後のドキュメント更新で詳細を提供予定です。

究極の AI 駆動 IDE 学習ガイド