defaultChromeNavigate property

  1. @visibleForTesting
Future<void> Function(int, String) get defaultChromeNavigate

Production Chrome Page.navigate: HTTP /json/version + WebSocket connect

  • JSON-RPC send + close. Inline duplicate of dusk's CdpClient at smaller scope (V1 cross-package inversion avoidance).

Implementation

@visibleForTesting
static Future<void> Function(int, String) get defaultChromeNavigate =>
    (port, url) async {
      // Page.navigate requires a TARGET (page) WebSocket, not the
      // BROWSER-level WS that /json/version exposes. /json lists every
      // attached target with its own webSocketDebuggerUrl. Pick the first
      // page-type target (the about:blank tab Chrome opened on launch).
      final client = HttpClient();
      String wsUrl;
      try {
        final req = await client.getUrl(
          Uri.parse('http://localhost:$port/json'),
        );
        final resp = await req.close();
        if (resp.statusCode != 200) {
          throw StateError(
            'Chrome /json returned ${resp.statusCode} on port $port.',
          );
        }
        final body = await resp.transform(utf8.decoder).join();
        final targets = jsonDecode(body) as List<dynamic>;
        final pageTarget =
            targets.whereType<Map<String, dynamic>>().firstWhere(
                  (t) => t['type'] == 'page',
                  orElse: () => throw StateError(
                    'No page-type target in Chrome /json on port $port.',
                  ),
                );
        wsUrl = pageTarget['webSocketDebuggerUrl'] as String;
      } finally {
        client.close(force: true);
      }

      final ws = await WebSocket.connect(wsUrl);
      try {
        final completer = Completer<void>();
        ws.listen(
          (raw) {
            if (completer.isCompleted) return;
            try {
              final decoded = jsonDecode(raw as String);
              if (decoded is Map && decoded['id'] == 1) {
                if (decoded['error'] != null) {
                  completer.completeError(StateError(
                    'CDP Page.navigate failed: ${decoded['error']}',
                  ));
                } else {
                  completer.complete();
                }
              }
            } catch (e, st) {
              completer.completeError(e, st);
            }
          },
          onError: completer.completeError,
          onDone: () {
            if (!completer.isCompleted) {
              completer.completeError(
                StateError('CDP WebSocket closed before Page.navigate ack.'),
              );
            }
          },
          cancelOnError: true,
        );
        ws.add(jsonEncode(<String, dynamic>{
          'id': 1,
          'method': 'Page.navigate',
          'params': <String, dynamic>{'url': url},
        }));
        await completer.future.timeout(const Duration(seconds: 10));
      } finally {
        await ws.close();
      }
    };