defaultChromeNavigate property
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();
}
};