наверняка кто-то работал, но мне ваш пример на шарпе завести не удалось, постоянно null везде
Пожалуйста, напишите более подробно где null.
- Создать консольное приложение (проект) в VS [ConsoleApp1]
- Через NuGet подключить Selenium.WebDriver 4.0.0-rc2
- Скачать Chrome Driver v94 и закинуть его в папку с проектом [*ConsoleApp1\bin\Debug\net5.0*]
- Заменить код в файле Program.cs на мой, не забыв указать свой логин и пароль в Instagram
- Установить точку останов в конце функции Main (напр. на строке var failResponseBody)
- Готово к запуску.
Так как обновился пакет Selenium.WebDriver до 4.0.0-rc2, я обновил пример кода:
namespace SeleniumCDP
{
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
using DevToolsVer = OpenQA.Selenium.DevTools.V94;
class Program
{
private static string Username = "Username123";
private static string Password = "Password123";
private static IWebDriver Driver = null;
private static IDevTools Tools = null;
private static IDevToolsSession Session = null;
private static DevToolsVer.DevToolsSessionDomains Domains = null;
private static ConcurrentBag<Task<Response>> CollectionXHR = null;
public struct Response
{
public string RequestId { get; set; }
public string ResponseUrl { get; set; }
public long ResponseStatus { get; set; }
public bool ResponseBodySuccess { get; set; }
public string ResponseBody { get; set; }
}
private static async Task Main()
{
// Инициализация
Driver = new ChromeDriver();
Driver.Manage().Timeouts().ImplicitWait = new TimeSpan(0, 0, 10);
Driver.Manage().Timeouts().AsynchronousJavaScript = new TimeSpan(0, 0, 30);
Driver.Manage().Timeouts().PageLoad = new TimeSpan(0, 0, 30);
Driver.Manage().Window.Maximize();
Tools = Driver as IDevTools;
Session = Tools.GetDevToolsSession();
Domains = Session.GetVersionSpecificDomains<DevToolsVer.DevToolsSessionDomains>();
await Domains.Network.Enable(new DevToolsVer.Network.EnableCommandSettings());
// Подготавливаем хранилище данных
CollectionXHR = new ConcurrentBag<Task<Response>>();
// Включаем запись получаемых данных
Domains.Network.ResponseReceived += ResponseReceived;
// Какие-то действия на сайте
Instagram(Username, Password);
// Отключаем запись получаемых данных
Domains.Network.ResponseReceived -= ResponseReceived;
// Ожидаем завершение извлечения полученных данных
Task.WaitAll(CollectionXHR.ToArray());
// Количество неудачных извлечений полученных данных
var failResponseBody = CollectionXHR.Where(w => w.Result.ResponseBodySuccess == false).Count();
// Выводим данные неудачных извлечений полученных данных
string failDescr = string.Empty;
foreach (var i in CollectionXHR.Where(w => w.Result.ResponseBodySuccess == false).ToList())
{
failDescr += $"RequestId = {i.Result.RequestId} | ResponseStatus = {i.Result.ResponseStatus} | ResponseBodySuccess = {i.Result.ResponseBodySuccess} \n";
}
}
private static void ResponseReceived(object sender, DevToolsVer.Network.ResponseReceivedEventArgs e)
{
if (e.Type == DevToolsVer.Network.ResourceType.XHR)
{
CollectionXHR.Add(GetResponseBodyAsync(e));
}
}
private static async Task<Response> GetResponseBodyAsync(DevToolsVer.Network.ResponseReceivedEventArgs e)
{
try
{
var cmd = new DevToolsVer.Network.GetResponseBodyCommandSettings();
cmd.RequestId = e.RequestId;
var data = await Domains.Network.GetResponseBody(cmd);
return new Response()
{
RequestId = e.RequestId,
ResponseUrl = e.Response.Url,
ResponseStatus = e.Response.Status,
ResponseBodySuccess = true,
ResponseBody = data.Body
};
}
catch
{
return new Response()
{
RequestId = e.RequestId,
ResponseUrl = e.Response.Url,
ResponseStatus = e.Response.Status,
ResponseBodySuccess = false,
ResponseBody = null
};
}
}
private static void Instagram(string username, string password)
{
// Переходим на instagram.com
Driver.Navigate().GoToUrl("https://www.instagram.com/");
// Вводим логин и пароль
{
var byUsernameInput = By.XPath("//form[@id='loginForm']//input[@name='username']");
var byPasswordInput = By.XPath("//form[@id='loginForm']//input[@name='password']");
var byLoginButton = By.XPath("//form[@id='loginForm']//button[@type='submit']");
if (Driver.FindElements(byLoginButton).Count > 0)
{
Driver.FindElement(byUsernameInput).SendKeys(username);
Driver.FindElement(byPasswordInput).SendKeys(password);
Driver.FindElement(byLoginButton).Click();
}
}
// Отказываемся от сохранения данных для входа
{
var byNotAutoLoginButton = By.XPath("//button[contains(text(),'Не сейчас')]");
if (Driver.FindElements(byNotAutoLoginButton).Count > 0)
{
Driver.FindElement(byNotAutoLoginButton).Click();
}
}
// Отказываемся от включения уведомлений
{
var byNotNotifyButton = By.XPath("//button[contains(text(),'Не сейчас')]");
if (Driver.FindElements(byNotNotifyButton).Count > 0)
{
Driver.FindElement(byNotNotifyButton).Click();
}
}
// Переходим в директ
{
var byMessengerLink = By.XPath("//a[@href='/direct/inbox/']");
if (Driver.FindElements(byMessengerLink).Count > 0)
{
Driver.FindElement(byMessengerLink).Click();
}
}
}
}
}
@MrKamaBullet05 можно два вопроса -
- чего именно хотите добиться ?
- почему именно на c# ? а не (например) на джава
взял wikipedia страницу стал мышью водить получил много XHR вызовов
попробовал всякие методы и событияFetch
и после того как получилresponseBody
он
в общеи похож на base64 строку но на самом деле нет…
точнее некоторые дают осмысленный текст но зваершаются invalid input
код сырой (написан из спортивного интереса) могу показать если кому интересно
на Java 8
кстати ваш код это случайно не реплика вот этого поста ?
unread,
Sep 28, 2021, 5:39:20 AM
to Selenium Users
How to capture network (XHR)? You can track the desired packs (XHR) in the Chrome (Open DevTools (F12) → Open tab “Network” → Select filter “Fetch/XHR”).
I found solutions where BrowserMob Proxy is used to capture traffic. But I wanted to use Selenium 4 which can work with CDP (Chrome DevTools Protocol).
I can successfully receive XHR packs (status 200). But I cannot get the body (getResponseBody) of some XHR packs.
https://groups.google.com/g/selenium-users/c/OueDjaEqp2U
Здравствуйте!
Есть какое то другое решение для логирования все входящих/исходящих запросов через selenium c# ? Т.к. через текущее решение большинство запросов попросту не логируется(они там отсутствуют, хотя в devtools они есть).
Может есть какое то решение выгружать весь HAR после проведения манипуляций с Driver.Navigate() ?
было и до Selenium 4.x через джаваскрипт
var performance = window.performance;
var timings = performance.timing;
return timings;
для XHR специально не смотрел
Selenium 4 XHR Java работает нормально помоему
Здравствуйте Сергей, прошу прощения, не могли бы вы привести пример как это все воплотить на C# ?
А то я не силен в этом, только учусь работать с selenium и никак не могу разобратся(
Цель : допустим мы сделали запрос Driver.Navigate().GoToUrl(“https://localhost”);
Вопрос: как сохранить весь HAR из devtools (включая все данные, запросы/ответы/хидеры и тд)?
Я делал так:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference("performance", LogLevel.All);
***запросы***
var logs = Driver.Manage().Logs.GetLog("performance");
for (int i = 0; i < logs.Count; i++)
{
Write_Succeeded(logs[i].Message);
}
Да я получаю запросы, НО я не вижу ответы от сервера Response.body
Как все это можно реализовать ?
много несколько лет назад работал с page perfomance.
это не весь хар только время:
для хрома
кстати но это только мое мнение просьба дать все и сразу кажется неуместной практически в 100% случаев
пример на джава 8
активизируем Fetch
открываем википедию напр XMLHttpRequest - Wikipedia
мышкой проводим над ссыоками
слушаем все XHR на которые пришел ответ
печатаем заголовки запроса и ответа и тело:
лог
in Fetch.requestPaused
listener. id:interception-job-29.0
URL: none
request headers:
content-type: application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"
cache-control: s-maxage=1209600
max-age=300, content-language: en
vary: Accept-Encoding
content-location: https://en.wikipedia.org/api/rest_v1/page/summary/XHR-FM
access-control-allow-origin: *
access-control-allow-methods: GET,HEAD
access-control-allow-headers: accept, content-type, content-length, cache-control, accept-language, api-user-agent, if-match, if-modified-since, if-none-match, dnt, accept-encoding
access-control-expose-headers: etag
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: origin-when-cross-origin
x-xss-protection: 1; mode=block
content-security-policy: default-src 'none'; frame-ancestors 'none'
x-content-security-policy: default-src 'none'; frame-ancestors 'none'
x-webkit-csp: default-src 'none'; frame-ancestors 'none'
server: restbase1028, date: Tue, 12 Oct 2021 15:59:30 GMT
etag: W/"1040156904/4cb79470-2b6a-11ec-8ab9-25ccec7ad2ca"
content-encoding: gzip
age: 37981
x-cache: cp1089 hit, cp1089 hit/11
x-cache-status: hit-front
server-timing: cache;desc="hit-front", host;desc="cp1089"
strict-transport-security: max-age=106384710; includeSubDomains; preload
report-to: { "group": "wm_nel", "max_age": 86400, "endpoints": [{ "url": "https://intake-logging.wikimedia.org/v1/events?stream=w3c.reportingapi.network_error&schema_uri=/w3c/reportingapi/network_error/1.0.0" }] }, nel: { "report_to": "wm_nel", "max_age": 86400, "failure_fraction": 0.05, "success_fraction": 0.0}
permissions-policy: interest-cohort=()
x-client-ip: 69.138.102.136,
accept-ranges: bytes
content-length: 614
response status: 200
response headers:
date: Wed, 13 Oct 2021 01:48:31 GMT
content-location: https://en.wikipedia.org/api/rest_v1/page/summary/Hypertext_Transfer_Protocol
cache-control: s-maxage=1209600
max-age=300
vary: Accept-Encoding
server: ATS/8.0.8, etag: W/"1049481372/8c828e80-2b05-11ec-86dd-8f0f28473208"
content-type: application/json; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/Summary/1.5.0"
content-language: en
access-control-allow-origin: *
access-control-allow-methods: GET,HEAD
access-control-allow-headers: accept, content-type, content-length, cache-control, accept-language, api-user-agent, if-match, if-modified-since, if-none-match, dnt, accept-encoding
access-control-expose-headers: etag
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN, referrer-policy: origin-when-cross-origin
x-xss-protection: 1; mode=block, content-security-policy: default-src 'none'; frame-ancestors 'none'
x-content-security-policy: default-src 'none'; frame-ancestors 'none'
x-webkit-csp: default-src 'none'; frame-ancestors 'none'
content-encoding: gzip, age: 2642
x-cache: cp1079 hit, cp1089 hit/13
x-cache-status: hit-front, server-timing: cache;desc="hit-front", host;desc="cp1089"
strict-transport-security: max-age=106384710; includeSubDomains; preload
report-to: { "group": "wm_nel", "max_age": 86400, "endpoints": [{ "url": "https://intake-logging.wikimedia.org/v1/events?stream=w3c.reportingapi.network_error&schema_uri=/w3c/reportingapi/network_error/1.0.0" }] }
nel: { "report_to": "wm_nel", "max_age": 86400, "failure_fraction": 0.05, "success_fraction": 0.0}
permissions-policy: interest-cohort=()
x-client-ip: 69.138.102.136
accept-ranges: bytes
content-length: 773
response body:
{
"type": "standard",
"title": "Hypertext Transfer Protocol",
"displaytitle": "Hypertext Transfer Protocol",
"namespace": {
"id": 0,
"text": ""
},
"wikibase_item": "Q8777",
"titles": {
"canonical": "Hypertext_Transfer_Protocol",
"normalized": "Hypertext Transfer Protocol",
"display": "Hypertext Transfer Protocol"
},
"pageid": 13443,
"thumbnail": {
"source": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/HTTP_logo.svg/320px-HTTP_logo.svg.png",
"width": 320,
"height": 171
},
"originalimage": {
"source": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/HTTP_logo.svg/512px-HTTP_logo.svg.png",
"width": 512,
"height": 274
},
"lang": "en",
"dir": "ltr",
"revision": "1049481372",
"tid": "8c22e250-2b05-11ec-8e0e-5b88d058b70b",
"timestamp": "2021-10-12T02:38:47Z",
"description": "Application protocol for distributed, collaborative, hypermedia information systems",
"description_source": "local",
"content_urls": {
"desktop": {
"page": "https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol",
"revisions": "https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol?action=history",
"edit": "https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol?action=edit",
"talk": "https://en.wikipedia.org/wiki/Talk:Hypertext_Transfer_Protocol"
},
"mobile": {
"page": "https://en.m.wikipedia.org/wiki/Hypertext_Transfer_Protocol",
"revisions": "https://en.m.wikipedia.org/wiki/Special:History/Hypertext_Transfer_Protocol",
"edit": "https://en.m.wikipedia.org/wiki/Hypertext_Transfer_Protocol?action=edit",
"talk": "https://en.m.wikipedia.org/wiki/Talk:Hypertext_Transfer_Protocol"
}
},
"extract": "The Hypertext Transfer Protocol (HTTP) is an application layer protocol in the Internet protocol suite model for distributed, collaborative, hypermedia information systems. HTTP is the foundation of data communication for the World Wide Web, where hypertext documents include hyperlinks to other resources that the user can easily access, for example by a mouse click or by tapping the screen in a web browser.",
"extract_html": "<p>The <b>Hypertext Transfer Protocol</b> (<b>HTTP</b>) is an application layer protocol in the Internet protocol suite model for distributed, collaborative, hypermedia information systems. HTTP is the foundation of data communication for the World Wide Web, where hypertext documents include hyperlinks to other resources that the user can easily access, for example by a mouse click or by tapping the screen in a web browser.</p>"
}
код
@Before
public void beforeTest() throws Exception {
List<RequestPattern> reqPattern = new ArrayList<>();
RequestPattern xhrReqPattern = new RequestPattern(Optional.of("*"),
Optional.of(ResourceType.XHR), Optional.of(RequestStage.RESPONSE));
reqPattern.add(xhrReqPattern);
chromeDevTools
.send(Fetch.enable(Optional.of(reqPattern), Optional.of(false)));
}
@After
public void afterTest() throws Exception {
chromeDevTools.send(Fetch.disable());
}
@Test
public void test() {
// Arrange
try {
chromeDevTools.addListener(Fetch.requestPaused(), event -> {
try {
List<HeaderEntry> headerEntries = event.getResponseHeaders()
.isPresent() ? event.getResponseHeaders().get()
: new ArrayList<>();
List<String> headers = headerEntries.stream().map(entry -> String
.format("%s: %s", entry.getName(), entry.getValue()))
.collect(Collectors.toList());
System.err.println("in Fetch.requestPaused listener. id:"
+ event.getRequestId().toString() + "\tURL: "
+ (event.getRequest().getUrlFragment().isPresent()
? event.getRequest().getUrlFragment().get() : "none")
+ "\trequest headers: " + event.getRequest().getHeaders()
+ "\response status: " + event.getResponseStatusCode().get()
+ "\tresponse headers: "
+ (event.getResponseHeaders().isPresent() ? headers : "none")
+ "\tresource type: " + event.getResourceType());
// always empty
event.getRequest().getPostData().ifPresent((data) -> {
System.err.println("Post Data:\n" + data + "\n");
});
Fetch.GetResponseBodyResponse response = chromeDevTools
.send(Fetch.getResponseBody(event.getRequestId()));
try {
String decodedBody = new String(
Base64.decodeBase64(response.getBody().getBytes("UTF8")));
System.err.println("response body:\n" + decodedBody + "\n");
} catch (Exception e) {
System.err.println("Exception (ignored): " + e.toString());
}
chromeDevTools.send(
Fetch.continueRequest(event.getRequestId(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty()));
} catch (DevToolsException e) {
System.err.println("Web Driver exception (ignored): "
+ Utils.processExceptionMessage(e.getMessage()));
// org.openqa.selenium.devtools.DevToolsException:
// {"id":6,"error":{"code":-32602,"message":"Invalid
// InterceptionId."},"sessionId":"4515310FC6FFDECA0705C54441EFD84B"}
}
});
// Act
// hover the links in the main wikipedia document
driver.get(url);
Utils.sleep(1000);
List<WebElement> elements = driver.findElement(By.id("mw-content-text"))
.findElements(By.tagName("a"));
actions = new Actions(driver);
elements.stream().limit(count).forEach(element -> {
actions.moveToElement(element).build().perform();
Utils.sleep(1000);
});
} catch (WebDriverException e) {
System.err.println("Web Driver exception (ignored): "
+ Utils.processExceptionMessage(e.getMessage()));
} catch (Exception e) {
System.err.println("Exception: " + e.toString());
throw (new RuntimeException(e));
}
}
это один из тестов опубликован на
- чего именно хотите добиться ?
Получить все входящие сообщения XHR, так как в них больше информации чем отображается на странице.
почему именно на c# ? а не (например) на джава
Против Java ничего не имею, но мой основной язык разработки это C#.
кстати ваш код это случайно не реплика вот этого поста ? (https://groups.google.com/g/selenium-users/c/OueDjaEqp2U)
Да, это мой пост, уже начал спрашивать на иностранных ресурсах, но ответа пока нет.
активизируем
Fetch
Возможно дело в этом - я в своем примере не касаюсь/использую Fetch
. Вы, получаете данные подписавшись на событие Fetch.requestPaused, а я на событие Network.responseReceived
Сейчас рассмотрю ваш код на Java подробнее
у org.openqa.selenium.devtools.v93.network.model.DataReceived
нету нужной информации
# Fired when data chunk was received over the network.
event dataReceived
parameters
# Request identifier.
RequestId requestId
# Timestamp.
MonotonicTime timestamp
# Data chunk length.
integer dataLength
# Actual bytes received (might be less than dataLength for compressed encodings).
integer encodedDataLength
у
org.openqa.selenium.devtools.v93.network.model.DataReceived
Не совсем понял причем здесь DataReceived
, я использовал для получения XHR событие Network.responseReceived, брал из него ид requestId
и получал тело этих данных (см. мой метод GetResponseBodyAsync). Пакеты XHR я получал успешно (код 200), но вот тела этих пакетов не всегда выходило извлекать, в этом и кроется проблема, возможно нужно дожидаться еще какого-то события или же возможно все и так верно.
Сейчас “перекладываю” ваш код на C# (ипользую Fetch.requestPaused). Как будут какие-то результаты, то отпишусь здесь.
З.Ы.
На днях вышел Selenium 4, поэтому уже не V93, а V95.
у import org.openqa.selenium.devtools.v94.network.model.ResponseReceived
можно получать
Network.GetResponseBodyResponse response = chromeDevTools
.send(Network.getResponseBody(event.getRequestId()));
String body = response.getBody();
if (response.getBase64Encoded()) {
try {
body = new String(Base64.decodeBase64(body.getBytes("UTF8")));
} catch (UnsupportedEncodingException e) {
System.err.println("Exception (ignored): " + e.toString());
}
}
System.err.println("response body:\n" + body);
можно получать
Да, с помощью Network.responseReceived можно получать XHR пакеты. Если посмотрите мой код, то я подписался на событие Network.responseReceived, в котором пытаюсь получить данные (см. мой метод GetResponseBodyAsync
):
var cmd = new DevToolsVer.Network.GetResponseBodyCommandSettings();
cmd.RequestId = e.RequestId;
data = await Domains.Network.GetResponseBody(cmd);
Подчеркну тот факт, что извлечение “тел” (Network.getResponseBody), полученных сообщений, происходит асинхронно! Иначе, большое количество пакетов оказывается пропущено. Поэтому я подписываю свой метод ResponseReceived
на событие Network.responseReceived, внутри которого вызываю асинхронный метод GetResponseBodyAsync
, содержащий Network.getResponseBody
, который извлекает “тело”.
И нюанс в том, что ответы Network.Response успешные (Network.Response.Status = 200
), но извлечение (Network.getResponseBody)некоторых “тел” ответов вызывает ошибку. Именно поэтому, в моем методе GetResponseBodyAsync
все обернуто в try-catch. Возникающие ошибки при извлечение:
- Network.getResponseBody: No resource with given identifier found
- Network.getResponseBody: No data found for resource with given identifier
Возможно это нормальное явление?
в джава варианте то что со стороны javascript описано как метод
https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-getResponseBody
то вызватся через прокси ответа
Network.GetResponseBodyResponse response = chromeDevTools
.send(Network.getResponseBody(event.getRequestId()));
String body = response.getBody();
а то что описано как событие
https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-dataReceived
вызывается через колбек для орбаботчика
chromeDevTools.addListener(Network.dataReceived(), event -> {
System.err
.println(String.format("Network request %s data received at %s",
event.getRequestId(), event.getTimestamp()));
});
как написать второе напрямую через веб сокеты хотел бы сам узнать
наверное нужен poll
Попробуйте использовать domains.Network.RequestWillBeSent
У меня была аналогичная с автором задача и у меня не логировались нужные мне запросы. Решилось простым способом - перед отключением записи получаемых данных (Domains.Network.ResponseReceived -= ResponseReceived;) я добавил простую задержку Thread.Sleep и все мои запросы стали добавляться в коллекцию. Вдруг кому-то пригодится)
Автору спасибо!
Всем привет. Не проверял свою идею, но видел много логов от performance capability. Я видел в них события, когда некоторые запросы приходят в нескольких пакетах. Т.е. несколько пакетов потом собираются в один большой, который содержит полную информацию. Соответственно, предположение такое: если ответ на запрос большой и не влазит в один пакет то он потом не распакуется без ошибок, если не собрать все нужные пакеты до последнего.
у вас tcp\ip гарантирует доставку пакетов в заданном порядке, так что на разбивку информации в браузере по пакетам вам смотреть явно не стоит