From 1044ab0bf4d5d57a868d1d22e134a5850220c5c3 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Sat, 15 Jul 2023 10:30:25 -0500 Subject: [PATCH 1/3] WIP - explanation of http clients and what examples and what implementations are needed --- .../webdriver/drivers/executors.en.md | 9 - .../webdriver/drivers/http_client.en.md | 315 ++++++++++++++++++ 2 files changed, 315 insertions(+), 9 deletions(-) delete mode 100644 website_and_docs/content/documentation/webdriver/drivers/executors.en.md create mode 100644 website_and_docs/content/documentation/webdriver/drivers/http_client.en.md diff --git a/website_and_docs/content/documentation/webdriver/drivers/executors.en.md b/website_and_docs/content/documentation/webdriver/drivers/executors.en.md deleted file mode 100644 index 3657d1690b9c..000000000000 --- a/website_and_docs/content/documentation/webdriver/drivers/executors.en.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Command executors" -linkTitle: "Executors" -weight: 3 ---- - -These allow you to set various parameters for the HTTP library - -{{< alert-code />}} diff --git a/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md b/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md new file mode 100644 index 000000000000..b4d71efb2299 --- /dev/null +++ b/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md @@ -0,0 +1,315 @@ +--- +title: "HTTP Client Configuration" +linkTitle: "Http Client" +weight: 3 +--- + +One of the great things about WebDriver's design is that any programming language with an HTTP Library +can implement the protocol and be used to drive a browser. In Selenium, each of the bindings +has a default HTTP Library used to create an HTTP Client. +This Client is responsible for sending network requests and receiving the associated responses to communicate +with a driver or the grid. + + +## Overview + +Here's a brief explanation of the default setup in each language: + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +**background** + +The default client has changed several times (Apache http client; OkHttp; currently it is Async HTTP Library). +We are moving to the standard Java library, but the features Selenium requires were not added to the standard library until Java 11, +so we can not make it the default until we set Java 11 as the minimum required version, which is planned at the end of September 2023. + +We describe this here, but we should summarize it for the documentation rather than needing to point people to the blog post +because people care about the "what" more than they "why" +https://www.selenium.dev/blog/2022/using-java11-httpclient/ + +**configuration** + +Rather than having users adjust the http client settings with system properties, Java created a `ClientConfig` class +for Selenium 4.0. +{{< /tab >}} +{{< tab header="Python" >}} +Python switched from httplib to urllib3 to support asynchronous communication for CDP functionality. +Settings affecting the http client have been supported in constructors of various classes as well as with class methods +in the `RemoteConnection()` class itself. +{{< /tab >}} +{{< tab header="CSharp" >}} +.NET switched from HttpWebRequest to the standard library HttpClient. +Configuration is only possible for the timeout value which can be set in the driver constructor. +{{< /tab >}} +{{< tab header="Ruby" >}} +Ruby has always used the standard http library. +Rather than passing in a configuration, Ruby allows users to change behaviors by subclassing a provided wrapper class. +{{< /tab >}} +{{< tab header="JavaScript" >}} +Who can possibly understand the innerworkings of this language? +{{< /tab >}} +{{< tab header="Kotlin" >}} +Just do what Java does until told otherwise +{{< /tab >}} +{{< /tabpane >}} + + +## Default Client Usage + +HTTP Clients are more important for Remote Server connections so these examples will show how to use them with +the Grid: + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +Java only supports setting cient config when using the RemoteWebDriverBuilder: +```java +ClientConfig config = ClientConfig.defaultConfig() + +WebDriver driver = RemoteWebDriver + .builder() + .oneOf(new ChromeOptions()) + .config(config) + .build(); +``` +{{< /tab >}} +{{< tab header="Python" >}} +```py +config = ClientConfig() +driver = webdriver.Remote(client_config: config) +``` +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +```rb +http_client = Selenium::WebDriver::Remote::Http::Default.new +options = Selenium::WebDriver::Options.chrome +driver = Selenium::WebDriver.for :remote, options: chrome, http_client: http_client +``` +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-code >}} +{{< /tab >}} +{{< /tabpane >}} + + +## Keep Alive + +The default in Selenium 4.0 for all bindings is `true`. +This setting can dramatically improve performance with SSL over remote connections +It is not recommended to change this. + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< /tabpane >}} + +## Timeouts + +Two types of timeouts can apply and many clients use the same setting for both +Open timeout (or connection timeout), and Read Timeout. + +### Connection or Open +This is named different things in different bindings, but it only applies +occurs the first time the connection is negotiated. If keep-alive is true, then this only +applies the first time, if it is false it applies every time. +The default value is: ??? + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-code >}} +{{< /tab >}} +{{< /tabpane >}} + +### Read +Read timeout applies to every single request and determines how long it should wait for the host to respond. +Other timeouts (such as Page Load timeout or any command timeout on the grid or a service provided) that are higher +than the Read timeout will never be encountered. For improved information about what is happening, the +Read timeout should always be higher than these other timeouts. +The default value in all bindings as of Selenium 4.11 is: 120 seconds + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-code >}} +{{< /tab >}} +{{< /tabpane >}} + +## Max Redirects +Some service providers manage session availability by sending redirect requests when a session isn't available yet. +This value represents how many of these redirects the client will allow before stopping. +The default value in all bindings as of Selenium 4.11 is: 20 + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< /tabpane >}} + + +## Proxy +This is if there is a proxy on the client machine that is needed to connect to the driver, grid, or service provider. +For routing network traffic going into the browser, you must set a proxy in the Options class. + +Note: the proxy required in Java is different from the proxy required in Options; does that make sense? + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-code >}} +{{< /tab >}} +{{< /tabpane >}} + + +## Authentication +Most bindings you would just add the username and password to the proxy in use, but Java allows this to be +specified independently. Does this make sense? + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< /tabpane >}} + + +## Certificate Path +Python allows you to specify this. Do other bindings need it? + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< /tabpane >}} + + +### Filters +This is set in Java, but I have no idea what it does. + +{{< tabpane text=true langEqualsHeader=true >}} +{{< tab header="Java" >}} +{{< badge-code >}} +{{< /tab >}} +{{< tab header="Python" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< badge-implementation >}} +{{< /tab >}} +{{< /tabpane >}} From 4a5bb09b3114d15e384663421241fedb701678d5 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Sat, 15 Jul 2023 10:36:27 -0500 Subject: [PATCH 2/3] WIP - fix link so it will build --- .../content/documentation/webdriver/drivers/_index.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website_and_docs/content/documentation/webdriver/drivers/_index.en.md b/website_and_docs/content/documentation/webdriver/drivers/_index.en.md index a3e1e4d48ffb..07ba131f2220 100644 --- a/website_and_docs/content/documentation/webdriver/drivers/_index.en.md +++ b/website_and_docs/content/documentation/webdriver/drivers/_index.en.md @@ -15,7 +15,7 @@ The session is created automatically by initializing a new Driver class object. Each language allows a session to be created with arguments from one of these classes (or equivalent): * [Options]({{< ref "options.md" >}}) to describe the kind of session you want; default values are used for local, but this is required for remote -* Some form of [CommandExecutor]({{< ref "executors.md" >}}) (the implementation varies between languages) +* [HTTP Client]({{< ref "http_client.md" >}}) Either a client instance or a configuration value for how the http commands should be managed. * [Listeners]({{< ref "listeners.md" >}}) ### Local Driver From 03e46ff9da5ac9c8c454b0005b1ba5784d37cd72 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Sat, 15 Jul 2023 10:50:58 -0500 Subject: [PATCH 3/3] WIP - docsy formatting got harder than it needs to be --- .../webdriver/drivers/http_client.en.md | 243 +++++++++--------- 1 file changed, 120 insertions(+), 123 deletions(-) diff --git a/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md b/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md index b4d71efb2299..40fe5bbfa1be 100644 --- a/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md +++ b/website_and_docs/content/documentation/webdriver/drivers/http_client.en.md @@ -16,8 +16,8 @@ with a driver or the grid. Here's a brief explanation of the default setup in each language: {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} -**background** +{{% tab header="Java" %}} +**Background** The default client has changed several times (Apache http client; OkHttp; currently it is Async HTTP Library). We are moving to the standard Java library, but the features Selenium requires were not added to the standard library until Java 11, @@ -27,30 +27,31 @@ We describe this here, but we should summarize it for the documentation rather t because people care about the "what" more than they "why" https://www.selenium.dev/blog/2022/using-java11-httpclient/ -**configuration** +\ +**Configuration** Rather than having users adjust the http client settings with system properties, Java created a `ClientConfig` class for Selenium 4.0. -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} Python switched from httplib to urllib3 to support asynchronous communication for CDP functionality. Settings affecting the http client have been supported in constructors of various classes as well as with class methods in the `RemoteConnection()` class itself. -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} .NET switched from HttpWebRequest to the standard library HttpClient. Configuration is only possible for the timeout value which can be set in the driver constructor. -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} Ruby has always used the standard http library. Rather than passing in a configuration, Ruby allows users to change behaviors by subclassing a provided wrapper class. -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} Who can possibly understand the innerworkings of this language? -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} Just do what Java does until told otherwise -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -59,8 +60,8 @@ Just do what Java does until told otherwise HTTP Clients are more important for Remote Server connections so these examples will show how to use them with the Grid: -{{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{< tabpane langEqualsHeader=true >}} +{{% tab text=true header="Java" %}} Java only supports setting cient config when using the RemoteWebDriverBuilder: ```java ClientConfig config = ClientConfig.defaultConfig() @@ -71,29 +72,25 @@ WebDriver driver = RemoteWebDriver .config(config) .build(); ``` -{{< /tab >}} +{{% /tab %}} {{< tab header="Python" >}} -```py config = ClientConfig() driver = webdriver.Remote(client_config: config) -``` {{< /tab >}} -{{< tab header="CSharp" >}} +{{% tab text=true header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< tab header="Ruby" >}} -```rb http_client = Selenium::WebDriver::Remote::Http::Default.new options = Selenium::WebDriver::Options.chrome driver = Selenium::WebDriver.for :remote, options: chrome, http_client: http_client -``` {{< /tab >}} -{{< tab header="JavaScript" >}} +{{% tab text=true header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab text=true header="Kotlin" %}} {{< badge-code >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -104,24 +101,24 @@ This setting can dramatically improve performance with SSL over remote connectio It is not recommended to change this. {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} ## Timeouts @@ -136,24 +133,24 @@ applies the first time, if it is false it applies every time. The default value is: ??? {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-code >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} ### Read @@ -164,24 +161,24 @@ Read timeout should always be higher than these other timeouts. The default value in all bindings as of Selenium 4.11 is: 120 seconds {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-code >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} ## Max Redirects @@ -190,24 +187,24 @@ This value represents how many of these redirects the client will allow before s The default value in all bindings as of Selenium 4.11 is: 20 {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -218,24 +215,24 @@ For routing network traffic going into the browser, you must set a proxy in the Note: the proxy required in Java is different from the proxy required in Options; does that make sense? {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-code >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -244,24 +241,24 @@ Most bindings you would just add the username and password to the proxy in use, specified independently. Does this make sense? {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -269,24 +266,24 @@ specified independently. Does this make sense? Python allows you to specify this. Do other bindings need it? {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}} @@ -294,22 +291,22 @@ Python allows you to specify this. Do other bindings need it? This is set in Java, but I have no idea what it does. {{< tabpane text=true langEqualsHeader=true >}} -{{< tab header="Java" >}} +{{% tab header="Java" %}} {{< badge-code >}} -{{< /tab >}} -{{< tab header="Python" >}} +{{% /tab %}} +{{% tab header="Python" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="CSharp" >}} +{{% /tab %}} +{{% tab header="CSharp" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Ruby" >}} +{{% /tab %}} +{{% tab header="Ruby" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="JavaScript" >}} +{{% /tab %}} +{{% tab header="JavaScript" %}} {{< badge-implementation >}} -{{< /tab >}} -{{< tab header="Kotlin" >}} +{{% /tab %}} +{{% tab header="Kotlin" %}} {{< badge-implementation >}} -{{< /tab >}} +{{% /tab %}} {{< /tabpane >}}