fix: extract some option classes to top level (#273)

This commit is contained in:
Yury Semikhatsky 2021-02-09 17:15:09 -08:00 committed by GitHub
parent 7642097291
commit 429f2969aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 495 additions and 693 deletions

View File

@ -27,123 +27,11 @@ import java.util.function.Consumer;
* <p> A Browser is created via [{@code method: BrowserType.launch}]. An example of using a {@code Browser} to create a {@code Page}:
*/
public interface Browser extends AutoCloseable {
class VideoSize {
private final int width;
private final int height;
public VideoSize(int width, int height) {
this.width = width;
this.height = height;
}
public int width() {
return width;
}
public int height() {
return height;
}
}
void onDisconnected(Consumer<Browser> handler);
void offDisconnected(Consumer<Browser> handler);
class NewContextOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public VideoSize size;
RecordVideo() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(int width, int height) {
this.size = new VideoSize(width, height);
return this;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
@ -173,7 +61,7 @@ public interface Browser extends AutoCloseable {
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
@ -271,7 +159,7 @@ public interface Browser extends AutoCloseable {
return this;
}
public NewContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
this.httpCredentials = new HttpCredentials(username, password);
return this;
}
public NewContextOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
@ -298,17 +186,17 @@ public interface Browser extends AutoCloseable {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public NewContextOptions withProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
public NewContextOptions withRecordHar(RecordHar recordHar) {
this.recordHar = recordHar;
return this;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
public NewContextOptions withRecordVideo(RecordVideo recordVideo) {
this.recordVideo = recordVideo;
return this;
}
public NewContextOptions withStorageState(String storageState) {
this.storageState = storageState;
@ -343,100 +231,6 @@ public interface Browser extends AutoCloseable {
}
}
class NewPageOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public VideoSize size;
RecordVideo() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(int width, int height) {
this.size = new VideoSize(width, height);
return this;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
@ -466,7 +260,7 @@ public interface Browser extends AutoCloseable {
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
@ -564,7 +358,7 @@ public interface Browser extends AutoCloseable {
return this;
}
public NewPageOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
this.httpCredentials = new HttpCredentials(username, password);
return this;
}
public NewPageOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
@ -591,17 +385,17 @@ public interface Browser extends AutoCloseable {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public NewPageOptions withProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
public NewPageOptions withRecordHar(RecordHar recordHar) {
this.recordHar = recordHar;
return this;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
public NewPageOptions withRecordVideo(RecordVideo recordVideo) {
this.recordVideo = recordVideo;
return this;
}
public NewPageOptions withStorageState(String storageState) {
this.storageState = storageState;

View File

@ -35,24 +35,6 @@ import java.util.regex.Pattern;
* contexts don't write any browsing data to disk.
*/
public interface BrowserContext extends AutoCloseable {
class HTTPCredentials {
private final String username;
private final String password;
public HTTPCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public String username() {
return username;
}
public String password() {
return password;
}
}
void onClose(Consumer<BrowserContext> handler);
void offClose(Consumer<BrowserContext> handler);

View File

@ -26,48 +26,6 @@ import java.util.*;
*/
public interface BrowserType {
class LaunchOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public LaunchOptions done() {
return LaunchOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found
* [here](http://peter.sh/experiments/chromium-command-line-switches/).
@ -197,9 +155,9 @@ public interface BrowserType {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public LaunchOptions withProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public LaunchOptions withSlowMo(double slowMo) {
this.slowMo = slowMo;
@ -211,125 +169,6 @@ public interface BrowserType {
}
}
class LaunchPersistentContextOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
public class Size {
/**
* Video frame width.
*/
public int width;
/**
* Video frame height.
*/
public int height;
Size() {
}
public RecordVideo done() {
return RecordVideo.this;
}
public Size withWidth(int width) {
this.width = width;
return this;
}
public Size withHeight(int height) {
this.height = height;
return this;
}
}
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public Size size;
RecordVideo() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public Size setSize() {
this.size = new Size();
return this.size;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
@ -407,7 +246,7 @@ public interface BrowserType {
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
@ -553,7 +392,7 @@ public interface BrowserType {
return this;
}
public LaunchPersistentContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
this.httpCredentials = new HttpCredentials(username, password);
return this;
}
public LaunchPersistentContextOptions withIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
@ -588,17 +427,17 @@ public interface BrowserType {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public LaunchPersistentContextOptions withProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
public LaunchPersistentContextOptions withRecordHar(RecordHar recordHar) {
this.recordHar = recordHar;
return this;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
public LaunchPersistentContextOptions withRecordVideo(RecordVideo recordVideo) {
this.recordVideo = recordVideo;
return this;
}
public LaunchPersistentContextOptions withSlowMo(double slowMo) {
this.slowMo = slowMo;

View File

@ -156,7 +156,7 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
public ClickOptions withPosition(int x, int y) {
public ClickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public ClickOptions withTimeout(double timeout) {
@ -223,7 +223,7 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
public DblclickOptions withPosition(int x, int y) {
public DblclickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public DblclickOptions withTimeout(double timeout) {
@ -286,7 +286,7 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
public HoverOptions withPosition(int x, int y) {
public HoverOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public HoverOptions withTimeout(double timeout) {
@ -440,25 +440,6 @@ public interface ElementHandle extends JSHandle {
}
}
class TapOptions {
public class Position {
public double x;
public double y;
Position() {
}
public TapOptions done() {
return TapOptions.this;
}
public Position withX(double x) {
this.x = x;
return this;
}
public Position withY(double y) {
this.y = y;
return this;
}
}
/**
* Whether to bypass the [actionability](./actionability.md) checks. Defaults to {@code false}.
*/
@ -497,9 +478,9 @@ public interface ElementHandle extends JSHandle {
this.noWaitAfter = noWaitAfter;
return this;
}
public Position setPosition() {
this.position = new Position();
return this.position;
public TapOptions withPosition(Position position) {
this.position = position;
return this;
}
public TapOptions withTimeout(double timeout) {
this.timeout = timeout;

View File

@ -196,7 +196,7 @@ public interface Frame {
this.position = position;
return this;
}
public ClickOptions withPosition(int x, int y) {
public ClickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public ClickOptions withTimeout(double timeout) {
@ -263,7 +263,7 @@ public interface Frame {
this.position = position;
return this;
}
public DblclickOptions withPosition(int x, int y) {
public DblclickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public DblclickOptions withTimeout(double timeout) {
@ -396,7 +396,7 @@ public interface Frame {
this.position = position;
return this;
}
public HoverOptions withPosition(int x, int y) {
public HoverOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public HoverOptions withTimeout(double timeout) {
@ -600,25 +600,6 @@ public interface Frame {
}
}
class TapOptions {
public class Position {
public double x;
public double y;
Position() {
}
public TapOptions done() {
return TapOptions.this;
}
public Position withX(double x) {
this.x = x;
return this;
}
public Position withY(double y) {
this.y = y;
return this;
}
}
/**
* Whether to bypass the [actionability](./actionability.md) checks. Defaults to {@code false}.
*/
@ -657,9 +638,9 @@ public interface Frame {
this.noWaitAfter = noWaitAfter;
return this;
}
public Position setPosition() {
this.position = new Position();
return this.position;
public TapOptions withPosition(Position position) {
this.position = position;
return this;
}
public TapOptions withTimeout(double timeout) {
this.timeout = timeout;

View File

@ -295,7 +295,7 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
public ClickOptions withPosition(int x, int y) {
public ClickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public ClickOptions withTimeout(double timeout) {
@ -374,7 +374,7 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
public DblclickOptions withPosition(int x, int y) {
public DblclickOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public DblclickOptions withTimeout(double timeout) {
@ -590,7 +590,7 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
public HoverOptions withPosition(int x, int y) {
public HoverOptions withPosition(double x, double y) {
return withPosition(new Position(x, y));
}
public HoverOptions withTimeout(double timeout) {
@ -695,47 +695,6 @@ public interface Page extends AutoCloseable {
}
}
class PdfOptions {
public class Margin {
/**
* Top margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String top;
/**
* Right margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String right;
/**
* Bottom margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String bottom;
/**
* Left margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String left;
Margin() {
}
public PdfOptions done() {
return PdfOptions.this;
}
public Margin withTop(String top) {
this.top = top;
return this;
}
public Margin withRight(String right) {
this.right = right;
return this;
}
public Margin withBottom(String bottom) {
this.bottom = bottom;
return this;
}
public Margin withLeft(String left) {
this.left = left;
return this;
}
}
/**
* Display header and footer. Defaults to {@code false}.
*/
@ -821,9 +780,9 @@ public interface Page extends AutoCloseable {
this.landscape = landscape;
return this;
}
public Margin setMargin() {
this.margin = new Margin();
return this.margin;
public PdfOptions withMargin(Margin margin) {
this.margin = margin;
return this;
}
public PdfOptions withPageRanges(String pageRanges) {
this.pageRanges = pageRanges;
@ -906,47 +865,6 @@ public interface Page extends AutoCloseable {
}
}
class ScreenshotOptions {
public class Clip {
/**
* x-coordinate of top-left corner of clip area
*/
public double x;
/**
* y-coordinate of top-left corner of clip area
*/
public double y;
/**
* width of clipping area
*/
public double width;
/**
* height of clipping area
*/
public double height;
Clip() {
}
public ScreenshotOptions done() {
return ScreenshotOptions.this;
}
public Clip withX(double x) {
this.x = x;
return this;
}
public Clip withY(double y) {
this.y = y;
return this;
}
public Clip withWidth(double width) {
this.width = width;
return this;
}
public Clip withHeight(double height) {
this.height = height;
return this;
}
}
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
*/
@ -981,9 +899,9 @@ public interface Page extends AutoCloseable {
*/
public ScreenshotType type;
public Clip setClip() {
this.clip = new Clip();
return this.clip;
public ScreenshotOptions withClip(Clip clip) {
this.clip = clip;
return this;
}
public ScreenshotOptions withFullPage(boolean fullPage) {
this.fullPage = fullPage;
@ -1080,25 +998,6 @@ public interface Page extends AutoCloseable {
}
}
class TapOptions {
public class Position {
public double x;
public double y;
Position() {
}
public TapOptions done() {
return TapOptions.this;
}
public Position withX(double x) {
this.x = x;
return this;
}
public Position withY(double y) {
this.y = y;
return this;
}
}
/**
* Whether to bypass the [actionability](./actionability.md) checks. Defaults to {@code false}.
*/
@ -1137,9 +1036,9 @@ public interface Page extends AutoCloseable {
this.noWaitAfter = noWaitAfter;
return this;
}
public Position setPosition() {
this.position = new Position();
return this.position;
public TapOptions withPosition(Position position) {
this.position = position;
return this;
}
public TapOptions withTimeout(double timeout) {
this.timeout = timeout;

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class Clip {
/**
* x-coordinate of top-left corner of clip area
*/
public double x;
/**
* y-coordinate of top-left corner of clip area
*/
public double y;
/**
* width of clipping area
*/
public double width;
/**
* height of clipping area
*/
public double height;
public Clip(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Clip() {
}
public Clip withX(double x) {
this.x = x;
return this;
}
public Clip withY(double y) {
this.y = y;
return this;
}
public Clip withWidth(double width) {
this.width = width;
return this;
}
public Clip withHeight(double height) {
this.height = height;
return this;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class HttpCredentials {
public String username;
public String password;
public HttpCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public HttpCredentials() {
}
public HttpCredentials withUsername(String username) {
this.username = username;
return this;
}
public HttpCredentials withPassword(String password) {
this.password = password;
return this;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class Margin {
/**
* Top margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String top;
/**
* Right margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String right;
/**
* Bottom margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String bottom;
/**
* Left margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String left;
public Margin(String top, String right, String bottom, String left) {
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}
public Margin() {
}
public Margin withTop(String top) {
this.top = top;
return this;
}
public Margin withRight(String right) {
this.right = right;
return this;
}
public Margin withBottom(String bottom) {
this.bottom = bottom;
return this;
}
public Margin withLeft(String left) {
this.left = left;
return this;
}
}

View File

@ -17,23 +17,21 @@
package com.microsoft.playwright.options;
public class Position {
public int x;
public int y;
public double x;
public double y;
public Position(double x, double y) {
this.x = x;
this.y = y;
}
public Position() {
}
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public Position withX(int x) {
public Position withX(double x) {
this.x = x;
return this;
}
public Position withY(int y) {
public Position withY(double y) {
this.y = y;
return this;
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
public Proxy(String server, String bypass, String username, String password) {
this.server = server;
this.bypass = bypass;
this.username = username;
this.password = password;
}
public Proxy() {
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
import java.nio.file.Path;
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
public RecordHar(Boolean omitContent, Path path) {
this.omitContent = omitContent;
this.path = path;
}
public RecordHar() {
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
import java.nio.file.Path;
public class RecordVideo {
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public Size size;
public RecordVideo(Path dir, Size size) {
this.dir = dir;
this.size = size;
}
public RecordVideo() {
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(Size size) {
this.size = size;
return this;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class Size {
/**
* Video frame width.
*/
public int width;
/**
* Video frame height.
*/
public int height;
public Size(int width, int height) {
this.width = width;
this.height = height;
}
public Size() {
}
public Size withWidth(int width) {
this.width = width;
return this;
}
public Size withHeight(int height) {
this.height = height;
return this;
}
}

View File

@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.Proxy;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
@ -32,7 +33,7 @@ public class TestBrowserContextProxy extends TestBase {
// Hide base class method to provide extra option.
static void launchBrowser() {
BrowserType.LaunchOptions options = createLaunchOptions();
options.setProxy().withServer("per-context").done();
options.withProxy(new Proxy().withServer("per-context"));
launchBrowser(options);
}
@ -40,7 +41,7 @@ public class TestBrowserContextProxy extends TestBase {
void shouldThrowForMissingGlobalProxy() {
Browser browser = browserType.launch(createLaunchOptions());
try {
browser.newContext(new Browser.NewContextOptions().setProxy().withServer("localhost:" + server.PORT).done());
browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy().withServer("localhost:" + server.PORT)));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy"));
@ -61,8 +62,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
.withServer("localhost:" + server.PORT).done());
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
assertEquals("Served by the proxy", page.title());
@ -77,8 +78,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
.withServer("localhost:" + server.PORT).done());
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
page.navigate("http://non-existent-2.com/target.html");
@ -94,8 +95,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
.withServer("localhost:" + server.PORT).done());
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
@ -116,8 +117,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
.withServer("127.0.0.1:" + server.PORT).done());
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("127.0.0.1:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
@ -140,10 +141,10 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>" + auth.get(0) + "</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)
.withUsername("user")
.withPassword("secret").done());
.withPassword("secret")));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
assertEquals("Basic " + Base64.getEncoder().encodeToString("user:secret".getBytes()), page.title());
@ -163,13 +164,13 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("127.0.0.1:" + server.PORT)
// FYI: using long and weird domain names to avoid ATT DNS hijacking
// that resolves everything to some weird search results page.
//
// @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00
.withBypass("1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test").done());
.withBypass("1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test")));
Page page = context.newPage();
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
@ -209,8 +210,8 @@ public class TestBrowserContextProxy extends TestBase {
@Test
void doesLaunchWithoutAPort() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy()
.withServer("http://localhost").done());
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("http://localhost")));
context.close();
}
}

View File

@ -19,6 +19,7 @@ package com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.options.RecordHar;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -42,8 +43,8 @@ public class TestHar extends TestBase {
PageWithHar() throws IOException {
harFile = Files.createTempFile("test-", ".har");
context = browser.newContext(new Browser.NewContextOptions().setRecordHar()
.withPath(harFile).done().withIgnoreHTTPSErrors(true));
context = browser.newContext(new Browser.NewContextOptions().withRecordHar(new RecordHar()
.withPath(harFile)).withIgnoreHTTPSErrors(true));
page = context.newPage();
}
@ -73,7 +74,7 @@ public class TestHar extends TestBase {
@Test
void shouldThrowWithoutPath() {
try {
browser.newContext(new Browser.NewContextOptions().setRecordHar().done());
browser.newContext(new Browser.NewContextOptions().withRecordHar(new RecordHar()));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("recordHar.path: expected string, got undefined"));
@ -118,7 +119,7 @@ public class TestHar extends TestBase {
Path userDataDir = Files.createTempDirectory("user-data-dir-");
BrowserContext context = browserType.launchPersistentContext(userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setRecordHar().withPath(harPath).done().withIgnoreHTTPSErrors(true));
.withRecordHar(new RecordHar().withPath(harPath)).withIgnoreHTTPSErrors(true));
Page page = context.pages().get(0);
page.navigate("data:text/html,<title>Hello</title>");

View File

@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.Clip;
import org.junit.jupiter.api.Test;
import javax.imageio.ImageIO;
@ -43,7 +44,7 @@ public class TestPageScreenshot extends TestBase {
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
.setClip().withX(50).withY(100).withWidth(150).withHeight(100).done());
.withClip(new Clip().withX(50).withY(100).withWidth(150).withHeight(100)));
BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshot));
assertEquals(150, image.getWidth());
assertEquals(100, image.getHeight());

View File

@ -16,6 +16,8 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.RecordVideo;
import com.microsoft.playwright.options.Size;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@ -28,7 +30,7 @@ public class TestScreencast extends TestBase {
@Test
void shouldExposeVideoPath(@TempDir Path videosDir) {
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordVideo().withDir(videosDir).withSize(320, 240).done()
.withRecordVideo(new RecordVideo().withDir(videosDir).withSize(new Size(320, 240)))
.withViewport(320, 240));
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");

View File

@ -62,6 +62,10 @@ abstract class Element {
return parent.enums();
}
Map<String, NestedClass> topLevelClasses() {
return parent.topLevelClasses();
}
static String toTitle(String name) {
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
@ -211,8 +215,6 @@ class TypeRef extends Element {
if (parent instanceof Field) {
customType = toTitle(parent.jsonName);
} else {
// String typeExpression = typeExpression(jsonElement.getAsJsonObject());
// System.out.println("add(\"" + parentPath + "\", \"" + typeExpression + "\", \"" + typeExpression + "\");" );
customType = toTitle(parent.parent.jsonName) + toTitle(parent.jsonName);
}
} else {
@ -227,8 +229,12 @@ class TypeRef extends Element {
}
}
if (generatedType == GeneratedType.CLASS) {
typeScope().createNestedClass(customType, this, jsonElement.getAsJsonObject());
isNestedClass = true;
if (parent instanceof Field ) {
typeScope().createTopLevelClass(customType, this, jsonElement.getAsJsonObject());
} else {
typeScope().createNestedClass(customType, this, jsonElement.getAsJsonObject());
isNestedClass = true;
}
}
}
@ -427,6 +433,15 @@ abstract class TypeDefinition extends Element {
}
}
void createTopLevelClass(String name, Element parent, JsonObject jsonObject) {
Map<String, NestedClass> map = topLevelClasses();
if (map.containsKey(name)) {
// TODO: check equal
return;
}
map.put(name, new NestedClass(parent, name, jsonObject));
}
void createNestedClass(String name, Element parent, JsonObject jsonObject) {
for (NestedClass c : classes) {
if (c.name.equals(name)) {
@ -818,7 +833,7 @@ class Field extends Element {
output.add(offset + " this.position = position;");
output.add(offset + " return this;");
output.add(offset + "}");
output.add(offset + "public " + parentClass + " withPosition(int x, int y) {");
output.add(offset + "public " + parentClass + " withPosition(double x, double y) {");
output.add(offset + " return withPosition(new Position(x, y));");
output.add(offset + "}");
return;
@ -868,6 +883,7 @@ class Interface extends TypeDefinition {
private final List<Method> methods = new ArrayList<>();
private final List<Event> events = new ArrayList<>();
private final Map<String, Enum> enums;
private final Map<String, NestedClass> topLevelClasses;
static final String header = "/*\n" +
" * Copyright (c) Microsoft Corporation.\n" +
" *\n" +
@ -889,9 +905,10 @@ class Interface extends TypeDefinition {
private static Set<String> allowedBaseInterfaces = new HashSet<>(asList("Browser", "JSHandle", "BrowserContext"));
private static Set<String> autoCloseableInterfaces = new HashSet<>(asList("Playwright", "Browser", "BrowserContext", "Page"));
Interface(JsonObject jsonElement, Map<String, Enum> enums) {
Interface(JsonObject jsonElement, Map<String, Enum> enums, Map<String, NestedClass> topLevelClasses) {
super(null, jsonElement);
this.enums = enums;
this.topLevelClasses = topLevelClasses;
for (JsonElement item : jsonElement.getAsJsonArray("members")) {
JsonObject memberJson = item.getAsJsonObject();
switch (memberJson.get("kind").getAsString()) {
@ -916,6 +933,10 @@ class Interface extends TypeDefinition {
return enums;
}
Map<String, NestedClass> topLevelClasses() {
return topLevelClasses;
}
void writeTo(List<String> output, String offset) {
output.add(header);
if ("Playwright".equals(jsonName)) {
@ -1032,48 +1053,6 @@ class Interface extends TypeDefinition {
output.add("");
break;
}
case "BrowserContext": {
output.add(offset + "class HTTPCredentials {");
output.add(offset + " private final String username;");
output.add(offset + " private final String password;");
output.add("");
output.add(offset + " public HTTPCredentials(String username, String password) {");
output.add(offset + " this.username = username;");
output.add(offset + " this.password = password;");
output.add(offset + " }");
output.add("");
output.add(offset + " public String username() {");
output.add(offset + " return username;");
output.add(offset + " }");
output.add("");
output.add(offset + " public String password() {");
output.add(offset + " return password;");
output.add(offset + " }");
output.add(offset + "}");
output.add("");
break;
}
case "Browser": {
output.add(offset + "class VideoSize {");
output.add(offset + " private final int width;");
output.add(offset + " private final int height;");
output.add("");
output.add(offset + " public VideoSize(int width, int height) {");
output.add(offset + " this.width = width;");
output.add(offset + " this.height = height;");
output.add(offset + " }");
output.add("");
output.add(offset + " public int width() {");
output.add(offset + " return width;");
output.add(offset + " }");
output.add("");
output.add(offset + " public int height() {");
output.add(offset + " return height;");
output.add(offset + " }");
output.add(offset + "}");
output.add("");
break;
}
case "ElementHandle": {
output.add(offset + "class BoundingBox {");
output.add(offset + " public double x;");
@ -1158,6 +1137,9 @@ class NestedClass extends TypeDefinition {
}
void writeTo(List<String> output, String offset) {
if (asList("RecordHar", "RecordVideo").contains(name)) {
output.add("import java.nio.file.Path;");
}
String access = parent.typeScope() instanceof NestedClass ? "public " : "";
output.add(offset + access + "class " + name + " {");
String bodyOffset = offset + " ";
@ -1174,6 +1156,9 @@ class NestedClass extends TypeDefinition {
f.writeGetter(output, bodyOffset);
}
} else {
if (parent.parent instanceof Field) {
writeConstructor(output, bodyOffset);
}
writeBuilderMethods(output, bodyOffset);
if (asList("Browser.newContext.options",
"Browser.newPage.options",
@ -1186,13 +1171,8 @@ class NestedClass extends TypeDefinition {
private void writeBuilderMethods(List<String> output, String bodyOffset) {
if (parent.typeScope() instanceof NestedClass) {
NestedClass outer = (NestedClass) parent.typeScope();
output.add(bodyOffset + name + "() {");
output.add(bodyOffset + "public " + name + "() {");
output.add(bodyOffset + "}");
output.add(bodyOffset + "public " + outer.name + " done() {");
output.add(bodyOffset + " return " + outer.name + ".this;");
output.add(bodyOffset + "}");
output.add("");
}
for (Field f : fields) {
f.writeBuilderMethod(output, bodyOffset, name);
@ -1255,10 +1235,11 @@ public class ApiGenerator {
System.out.println("Writing files to: " + dir.getCanonicalPath());
filterOtherLangs(api);
Map<String, Enum> enums = new HashMap<>();
Map<String, NestedClass> topLevelClasses = new HashMap<>();
for (JsonElement entry: api) {
String name = entry.getAsJsonObject().get("name").getAsString();
List<String> lines = new ArrayList<>();
new Interface(entry.getAsJsonObject(), enums).writeTo(lines, "");
new Interface(entry.getAsJsonObject(), enums, topLevelClasses).writeTo(lines, "");
String text = String.join("\n", lines);
try (FileWriter writer = new FileWriter(new File(dir, name + ".java"))) {
writer.write(text);
@ -1274,6 +1255,15 @@ public class ApiGenerator {
writer.write(text);
}
}
for (NestedClass e : topLevelClasses.values()) {
List<String> lines = new ArrayList<>();
lines.add(Interface.header.replace("package com.microsoft.playwright;", "package com.microsoft.playwright.options;"));
e.writeTo(lines, "");
String text = String.join("\n", lines);
try (FileWriter writer = new FileWriter(new File(dir, e.name + ".java"))) {
writer.write(text);
}
}
}
private static void filterOtherLangs(JsonElement json) {

View File

@ -52,16 +52,6 @@ class Types {
add("Page.viewportSize", "Object|null", "Viewport", new Empty());
add("BrowserType.launchPersistentContext.options.viewport", "Object|null", "Page.Viewport", new Empty());
// RecordVideo size.
add("Browser.newContext.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("Browser.newPage.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("BrowserType.launchPersistentContext.recordVideo.size", "Object", "Browser.VideoSize", new Empty());
// HTTP credentials.
add("Browser.newContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("Browser.newPage.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserType.launchPersistentContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserContext.exposeBinding.callback", "function", "Page.Binding");
add("BrowserContext.exposeFunction.callback", "function", "Page.Function");
add("Page.exposeBinding.callback", "function", "Binding");
@ -75,17 +65,6 @@ class Types {
add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Object|null", "BoundingBox", new Empty());
// Custom options
add("Page.click.options.position", "Object", "Position", new Empty());
add("Page.dblclick.options.position", "Object", "Position", new Empty());
add("Page.hover.options.position", "Object", "Position", new Empty());
add("Frame.click.options.position", "Object", "Position", new Empty());
add("Frame.dblclick.options.position", "Object", "Position", new Empty());
add("Frame.hover.options.position", "Object", "Position", new Empty());
add("ElementHandle.click.options.position", "Object", "Position", new Empty());
add("ElementHandle.dblclick.options.position", "Object", "Position", new Empty());
add("ElementHandle.hover.options.position", "Object", "Position", new Empty());
// The method has custom signatures
add("BrowserContext.cookies", "Array<Object>", "Cookie");
add("BrowserContext.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());