具備 BiDi API 的 Chrome Devtools 通訊協定
這些範例目前使用 CDP 實作,但當功能使用 WebDriver-BiDi 重新實作時,相同的程式碼應該也能正常運作。
用法
隨著 Selenium 專案致力於支援實際使用案例,下列 API 清單將會持續增加。如果您希望看到其他功能,請提出 功能要求。
當這些範例使用 WebDriver-Bidi 通訊協定重新實作時,它們將移至 WebDriver Bidi 頁面。
範例
基本驗證
有些應用程式會使用瀏覽器驗證來保護頁面。過去常在 URL 中處理這些驗證,但瀏覽器已停止支援此功能。使用 BiDi,您現在可以在必要時提供憑證
可以在 CDP 端點基本驗證 和 CDP API 基本驗證 中找到其他實作。
Predicate<URI> uriPredicate = uri -> uri.toString().contains("herokuapp.com");
Supplier<Credentials> authentication = UsernameAndPassword.of("admin", "admin");
((HasAuthentication) driver).register(uriPredicate, authentication);
可以在 CDP 端點基本驗證 中找到其他實作。
var handler = new NetworkAuthenticationHandler()
{
UriMatcher = uri => uri.AbsoluteUri.Contains("herokuapp"),
Credentials = new PasswordCredentials("admin", "admin")
};
var networkInterceptor = driver.Manage().Network;
networkInterceptor.AddAuthenticationHandler(handler);
await networkInterceptor.StartMonitoring();
driver.register(username: 'admin',
password: 'admin',
uri: /herokuapp/)
const {Builder} = require('selenium-webdriver');
(async function example() {
try {
let driver = await new Builder()
.forBrowser('chrome')
.build();
const pageCdpConnection = await driver.createCDPConnection('page');
await driver.register('username', 'password', pageCdpConnection);
await driver.get('https://the-internet.herokuapp.com/basic_auth');
await driver.quit();
}catch (e){
console.log(e)
}
}())
val uriPredicate = Predicate { uri: URI ->
uri.host.contains("your-domain.com")
}
(driver as HasAuthentication).register(uriPredicate, UsernameAndPassword.of("admin", "password"))
driver.get("https://your-domain.com/login")
腳本指令碼
這在執行遠端伺服器時特別有用。例如,每當您檢查元素的可見性,或每當您使用傳統的 get 屬性方法時,Selenium 會將 js 檔案的內容傳送至腳本執行端點。這些檔案每個約 50kB,會累積起來。
var key = await new JavaScriptEngine(driver).PinScript("return arguments;");
var arguments = ((WebDriver)driver).ExecuteScript(key, 1, true, element);
key = driver.pin_script('return arguments;')
arguments = driver.execute_script(key, 1, true, element)
突變觀察
Mutation Observation 是在 DOM 中特定元素發生 DOM 變異時,透過 WebDriver BiDi 擷取事件的能力。
CopyOnWriteArrayList<WebElement> mutations = new CopyOnWriteArrayList<>();
((HasLogEvents) driver).onLogEvent(domMutation(e -> mutations.add(e.getElement())));
async with driver.bidi_connection() as session:
log = Log(driver, session)
var mutations = new List<IWebElement>();
using IJavaScriptEngine monitor = new JavaScriptEngine(driver);
monitor.DomMutated += (_, e) =>
{
var locator = By.CssSelector($"*[data-__webdriver_id='{e.AttributeData.TargetId}']");
mutations.Add(driver.FindElement(locator));
};
await monitor.StartEventMonitoring();
await monitor.EnableDomMutationMonitoring();
mutations = []
driver.on_log_event(:mutation) { |mutation| mutations << mutation.element }
const {Builder, until} = require('selenium-webdriver');
const assert = require("assert");
(async function example() {
try {
let driver = await new Builder()
.forBrowser('chrome')
.build();
const cdpConnection = await driver.createCDPConnection('page');
await driver.logMutationEvents(cdpConnection, event => {
assert.deepStrictEqual(event['attribute_name'], 'style');
assert.deepStrictEqual(event['current_value'], "");
assert.deepStrictEqual(event['old_value'], "display:none;");
});
await driver.get('dynamic.html');
await driver.findElement({id: 'reveal'}).click();
let revealed = driver.findElement({id: 'revealed'});
await driver.wait(until.elementIsVisible(revealed), 5000);
await driver.quit();
}catch (e){
console.log(e)
}
}())
主控台記錄和錯誤
監聽 console.log
事件,並註冊呼叫回函來處理事件。
CDP API 主控台記錄 和 WebDriver BiDi 主控台記錄
使用 WebDriver BiDi 主控台記錄 實作。HasLogEvents
可能最後會被棄用,因為它沒有實作 Closeable
。
CopyOnWriteArrayList<String> messages = new CopyOnWriteArrayList<>();
((HasLogEvents) driver).onLogEvent(consoleEvent(e -> messages.add(e.getMessages().get(0))));
async with driver.bidi_connection() as session:
log = Log(driver, session)
async with log.add_listener(Console.ALL) as messages:
using IJavaScriptEngine monitor = new JavaScriptEngine(driver);
var messages = new List<string>();
monitor.JavaScriptConsoleApiCalled += (_, e) =>
{
messages.Add(e.MessageContent);
};
await monitor.StartEventMonitoring();
logs = []
driver.on_log_event(:console) { |log| logs << log.args.first }
const {Builder} = require('selenium-webdriver');
(async () => {
try {
let driver = new Builder()
.forBrowser('chrome')
.build();
const cdpConnection = await driver.createCDPConnection('page');
await driver.onLogEvent(cdpConnection, function (event) {
console.log(event['args'][0]['value']);
});
await driver.executeScript('console.log("here")');
await driver.quit();
}catch (e){
console.log(e);
}
})()
fun kotlinConsoleLogExample() {
val driver = ChromeDriver()
val devTools = driver.devTools
devTools.createSession()
val logConsole = { c: ConsoleEvent -> print("Console log message is: " + c.messages)}
devTools.domains.events().addConsoleListener(logConsole)
driver.get("https://www.google.com")
val executor = driver as JavascriptExecutor
executor.executeScript("console.log('Hello World')")
val input = driver.findElement(By.name("q"))
input.sendKeys("Selenium 4")
input.sendKeys(Keys.RETURN)
driver.quit()
}
JavaScript 例外
監聽 JS 例外狀況,並註冊呼叫回函來處理例外狀況詳細資料。
async with driver.bidi_connection() as session:
log = Log(driver, session)
async with log.add_js_error_listener() as messages:
using IJavaScriptEngine monitor = new JavaScriptEngine(driver);
var messages = new List<string>();
monitor.JavaScriptExceptionThrown += (_, e) =>
{
messages.Add(e.Message);
};
await monitor.StartEventMonitoring();
exceptions = []
driver.on_log_event(:exception) { |exception| exceptions << exception }
網路攔截
要求和回應都可以被記錄或轉換。
回應資訊
CopyOnWriteArrayList<String> contentType = new CopyOnWriteArrayList<>();
try (NetworkInterceptor ignored =
new NetworkInterceptor(
driver,
(Filter)
next ->
req -> {
HttpResponse res = next.execute(req);
contentType.add(res.getHeader("Content-Type"));
return res;
})) {
var contentType = new List<string>();
INetwork networkInterceptor = driver.Manage().Network;
networkInterceptor.NetworkResponseReceived += (_, e) =>
{
contentType.Add(e.ResponseHeaders["content-type"]);
};
await networkInterceptor.StartMonitoring();
content_type = []
driver.intercept do |request, &continue|
continue.call(request) do |response|
content_type << response.headers['content-type']
end
end
回應轉換
try (NetworkInterceptor ignored =
new NetworkInterceptor(
driver,
Route.matching(req -> true)
.to(
() ->
req ->
new HttpResponse()
.setStatus(200)
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(Contents.utf8String("Creamy, delicious cheese!"))))) {
var handler = new NetworkResponseHandler()
{
ResponseMatcher = _ => true,
ResponseTransformer = _ => new HttpResponseData
{
StatusCode = 200,
Body = "Creamy, delicious cheese!"
}
};
INetwork networkInterceptor = driver.Manage().Network;
networkInterceptor.AddResponseHandler(handler);
await networkInterceptor.StartMonitoring();
driver.intercept do |request, &continue|
continue.call(request) do |response|
response.body = 'Creamy, delicious cheese!' if request.url.include?('blank')
end
end
const connection = await driver.createCDPConnection('page')
let url = fileServer.whereIs("/cheese")
let httpResponse = new HttpResponse(url)
httpResponse.addHeaders("Content-Type", "UTF-8")
httpResponse.body = "sausages"
await driver.onIntercept(connection, httpResponse, async function () {
let body = await driver.getPageSource()
assert.strictEqual(body.includes("sausages"), true, `Body contains: ${body}`)
})
driver.get(url)
val driver = ChromeDriver()
val interceptor = new NetworkInterceptor(
driver,
Route.matching(req -> true)
.to(() -> req -> new HttpResponse()
.setStatus(200)
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(utf8String("Creamy, delicious cheese!"))))
driver.get(appServer.whereIs("/cheese"))
String source = driver.getPageSource()
要求攔截
var handler = new NetworkRequestHandler
{
RequestMatcher = request => request.Url.Contains("one.js"),
RequestTransformer = request =>
{
request.Url = request.Url.Replace("one", "two");
return request;
}
};
INetwork networkInterceptor = driver.Manage().Network;
networkInterceptor.AddRequestHandler(handler);
await networkInterceptor.StartMonitoring();
driver.intercept do |request, &continue|
uri = URI(request.url)
request.url = uri.to_s.gsub('one', 'two') if uri.path&.end_with?('one.js')
continue.call(request)
end
最後修改時間 2023 年 11 月 17 日:升級至 Docsy 0 7 2 (#1529) (48f43616907)