1
0
mirror of synced 2026-05-23 11:13:15 +00:00

Compare commits

...

15 Commits

Author SHA1 Message Date
Yury Semikhatsky 414abb83fe chore: set release version to 1.38.0 (#1386) 2023-09-18 17:32:02 -07:00
Yury Semikhatsky 85c5f90029 chore: update selector escaping, roll driver (#1385) 2023-09-18 16:22:23 -07:00
railwayursin 30778a3b04 fix: setfiles OOM exception (#1384) 2023-09-18 15:48:56 -07:00
Max Schmitt 25ba8474f4 devops: fix publishing (#1378) 2023-09-18 15:41:38 -07:00
Yury Semikhatsky 5394b5d9b3 chore: roll driver 1.38.0-alpha-sep-10-2023 (#1380) 2023-09-12 09:10:13 -07:00
Yury Semikhatsky 883487772a Revert "feat(soft-assertions): Implement soft assertions for playwrig… (#1377)
Revert "feat(soft-assertions): Implement soft assertions for playwright-java (#1361)"

This reverts commit 86f929aaf0.
2023-09-06 15:27:30 -07:00
Yury Semikhatsky 4d912193e7 chore: support js Map, Set in protocol results (#1376) 2023-09-06 13:33:16 -07:00
Yury Semikhatsky 05eb1f1161 chore: roll 1.38.0-alpha-sep-5-2023 (#1374) 2023-09-05 11:21:45 -07:00
Oliver Weiler 2dbfa9d38e chore: Generate metadata for method parameters (#1370) 2023-09-01 09:30:52 -07:00
uchagani 86f929aaf0 feat(soft-assertions): Implement soft assertions for playwright-java (#1361) 2023-08-28 12:41:44 -07:00
Yury Semikhatsky fa75e29fcf chore: roll driver to 1.38.0-alpha-aug-23 (#1362)
Reference https://github.com/microsoft/playwright-java/issues/1353
2023-08-23 18:41:40 -07:00
Yury Semikhatsky 7d5953c96e Revert "SoftAssertions Implementation (#1340)" (#1357)
This reverts commit 632fba54a5.
2023-08-17 10:11:30 -07:00
uchagani 632fba54a5 SoftAssertions Implementation (#1340) 2023-08-17 10:06:04 -07:00
Yury Semikhatsky f76af33f52 docs: update ROLLING.md with instructions how to find driver version (#1356) 2023-08-17 10:04:38 -07:00
Yury Semikhatsky 4204096c24 chore: set dev version to 1.38 (#1350) 2023-08-11 15:31:11 -07:00
48 changed files with 560 additions and 225 deletions
+6 -4
View File
@@ -46,15 +46,17 @@ steps:
done
displayName: 'Create .sha256 files'
- task: EsrpRelease@2
- task: EsrpRelease@4
inputs:
ConnectedServiceName: 'Playwright-Java-ESRP'
ConnectedServiceName: 'Playwright-ESRP'
Intent: 'PackageDistribution'
ContentType: 'Maven'
PackageLocation: './local-build'
ContentSource: 'Folder'
FolderLocation: './local-build'
WaitForReleaseCompletion: true
Owners: 'yurys@microsoft.com'
Approvers: 'maxschmitt@microsoft.com'
ServiceEndpointUrl: 'https://api.esrp.microsoft.com'
MainPublisher: 'PlaywrightJava'
MainPublisher: 'Playwright'
DomainTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47'
displayName: 'ESRP Release to Maven'
+2 -2
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->116.0.5845.82<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->117.0.5938.62<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->115.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->117.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
+7
View File
@@ -6,8 +6,15 @@
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll
### Finding driver version
For development versions of Playwright, you can find the latest version by looking at [publish_canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) workflow -> `publish canary NPM & Publish canary Docker` -> `build & publish driver` step -> `PACKAGE_VERSION`
<img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75">
# Updating Version
```bash
./scripts/set_maven_version.sh 1.15.0
```
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
</parent>
<artifactId>driver</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -127,6 +127,16 @@ public interface BrowserContext extends AutoCloseable {
*/
void offPage(Consumer<Page> handler);
/**
* Emitted when exception is unhandled in any of the pages in this context. To listen for errors from a particular page,
* use {@link Page#onPageError Page.onPageError()} instead.
*/
void onWebError(Consumer<WebError> handler);
/**
* Removes handler that was previously added with {@link #onWebError onWebError(handler)}.
*/
void offWebError(Consumer<WebError> handler);
/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only
* listen for requests from a particular page, use {@link Page#onRequest Page.onRequest()}.
@@ -24,14 +24,16 @@ import java.nio.file.Path;
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <p> Download event is emitted once the download starts. Download path becomes available once download completes.
* <pre>{@code
* // wait for download to start
* // Wait for the download to start
* Download download = page.waitForDownload(() -> {
* page.getByText("Download file").click();
* // Perform the action that initiates download
* page.getByText("Download file").click();
* });
* // wait for download to complete
* Path path = download.path();
*
* // Wait for the download process to complete and save the downloaded file somewhere
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*/
public interface Download {
@@ -80,6 +82,11 @@ public interface Download {
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
*
* <p> **Usage**
* <pre>{@code
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*
* @param path Path where the download should be copied.
* @since v1.8
*/
@@ -1582,7 +1582,7 @@ public interface ElementHandle extends JSHandle {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.8
@@ -1600,7 +1600,7 @@ public interface ElementHandle extends JSHandle {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.8
@@ -2450,24 +2450,9 @@ public interface ElementHandle extends JSHandle {
*/
String textContent();
/**
* Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link ElementHandle#press
* ElementHandle.press()}.
*
* <p> **Usage**
* <pre>{@code
* elementHandle.type("Hello"); // Types instantly
* elementHandle.type("World", new ElementHandle.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* ElementHandle elementHandle = page.querySelector("input");
* elementHandle.type("some text");
* elementHandle.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.8
@@ -2476,24 +2461,9 @@ public interface ElementHandle extends JSHandle {
type(text, null);
}
/**
* Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link ElementHandle#press
* ElementHandle.press()}.
*
* <p> **Usage**
* <pre>{@code
* elementHandle.type("Hello"); // Types instantly
* elementHandle.type("World", new ElementHandle.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* ElementHandle elementHandle = page.querySelector("input");
* elementHandle.type("some text");
* elementHandle.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.8
@@ -2987,7 +2987,7 @@ public interface Frame {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -3006,7 +3006,7 @@ public interface Frame {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -4677,19 +4677,9 @@ public interface Frame {
*/
String title();
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* frame.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Frame#fill
* Frame.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -4699,19 +4689,9 @@ public interface Frame {
type(selector, text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* frame.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Frame#fill
* Frame.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -132,7 +132,9 @@ public interface Keyboard {
*/
void insertText(String text);
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
@@ -175,7 +177,9 @@ public interface Keyboard {
press(key, null);
}
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
@@ -216,7 +220,11 @@ public interface Keyboard {
*/
void press(String key, PressOptions options);
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
@@ -239,7 +247,11 @@ public interface Keyboard {
type(text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
@@ -1388,6 +1388,50 @@ public interface Locator {
return this;
}
}
class PressSequentiallyOptions {
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
* Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public PressSequentiallyOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public PressSequentiallyOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
* Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
*/
public PressSequentiallyOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ScreenshotOptions {
/**
* When set to {@code "disabled"}, stops CSS animations, CSS transitions and Web Animations. Animations get different
@@ -2832,7 +2876,7 @@ public interface Locator {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Locator#type Locator.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.14
@@ -2859,7 +2903,7 @@ public interface Locator {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Locator#type Locator.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.14
@@ -3956,6 +4000,60 @@ public interface Locator {
* @since v1.14
*/
void press(String key, PressOptions options);
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* locator.pressSequentially("Hello"); // Types instantly
* locator.pressSequentially("World", new Locator.pressSequentiallyOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator locator = page.getByLabel("Password");
* locator.pressSequentially("my password");
* locator.press("Enter");
* }</pre>
*
* @param text String of characters to sequentially press into a focused element.
* @since v1.38
*/
default void pressSequentially(String text) {
pressSequentially(text, null);
}
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* locator.pressSequentially("Hello"); // Types instantly
* locator.pressSequentially("World", new Locator.pressSequentiallyOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator locator = page.getByLabel("Password");
* locator.pressSequentially("my password");
* locator.press("Enter");
* }</pre>
*
* @param text String of characters to sequentially press into a focused element.
* @since v1.38
*/
void pressSequentially(String text, PressSequentiallyOptions options);
/**
* Take a screenshot of the element matching the locator.
*
@@ -4865,26 +4963,9 @@ public interface Locator {
*/
String textContent(TextContentOptions options);
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to type characters if there is
* special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* element.type("Hello"); // Types instantly
* element.type("World", new Locator.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.14
@@ -4893,26 +4974,9 @@ public interface Locator {
type(text, null);
}
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to type characters if there is
* special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* element.type("Hello"); // Types instantly
* element.type("World", new Locator.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.14
@@ -4535,7 +4535,7 @@ public interface Page extends AutoCloseable {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Page#type Page.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -4554,7 +4554,7 @@ public interface Page extends AutoCloseable {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Page#type Page.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -6942,19 +6942,9 @@ public interface Page extends AutoCloseable {
*/
Touchscreen touchscreen();
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* page.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Page#fill
* Page.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* page.type("#mytextarea", "Hello");
* // Types slower, like a user
* page.type("#mytextarea", "World", new Page.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -6964,19 +6954,9 @@ public interface Page extends AutoCloseable {
type(selector, text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* page.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Page#fill
* Page.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* page.type("#mytextarea", "Hello");
* // Types slower, like a user
* page.type("#mytextarea", "World", new Page.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -62,6 +62,22 @@ public interface Request {
/**
* Returns the {@code Frame} that initiated this request.
*
* <p> **Usage**
* <pre>{@code
* String frameUrl = request.frame().url();
* }</pre>
*
* <p> **Details**
*
* <p> Note that in some cases the frame is not available, and this method will throw.
* <ul>
* <li> When request originates in the Service Worker. You can use {@code request.serviceWorker()} to check that.</li>
* <li> When navigation request is issued before the corresponding frame is created. You can use {@link
* Request#isNavigationRequest Request.isNavigationRequest()} to check that.</li>
* </ul>
*
* <p> Here is an example that handles all the cases:
*
* @since v1.8
*/
Frame frame();
@@ -91,6 +107,9 @@ public interface Request {
/**
* Whether this request is driving frame's navigation.
*
* <p> Some navigation requests are issued before the corresponding frame is created, and therefore do not have {@link
* Request#frame Request.frame()} available.
*
* @since v1.8
*/
boolean isNavigationRequest();
@@ -0,0 +1,47 @@
/*
* 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;
/**
* {@code WebError} class represents an unhandled exeception thrown in the page. It is dispatched via the {@link
* BrowserContext#onWebError BrowserContext.onWebError()} event.
* <pre>{@code
* // Log all uncaught errors to the terminal
* context.onWebError(webError -> {
* System.out.println("Uncaught exception: " + webError.error());
* });
*
* // Navigate to a page with an exception.
* page.navigate("data:text/html,<script>throw new Error('Test')</script>");
* }</pre>
*/
public interface WebError {
/**
* The page that produced this unhandled exception, if any.
*
* @since v1.38
*/
Page page();
/**
* Unhandled error that was thrown.
*
* @since v1.38
*/
String error();
}
@@ -82,6 +82,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
CONSOLE,
DIALOG,
PAGE,
WEBERROR,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
@@ -154,6 +155,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onWebError(Consumer<WebError> handler) {
listeners.add(EventType.WEBERROR, handler);
}
@Override
public void offWebError(Consumer<WebError> handler) {
listeners.remove(EventType.WEBERROR, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
@@ -645,6 +656,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = this;
handleRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -710,6 +722,25 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
PageImpl page;
try {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
} catch (PlaywrightException e) {
page = null;
}
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr));
if (page != null) {
page.listeners.notify(PageImpl.EventType.PAGEERROR, errorStr);
}
} else if ("close".equals(event)) {
didClose();
}
@@ -203,6 +203,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -223,6 +223,10 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -436,6 +436,11 @@ class LocatorImpl implements Locator {
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public void pressSequentially(String text, PressSequentiallyOptions options) {
type(text, convertType(options, TypeOptions.class));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
@@ -5,6 +5,7 @@ import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
public class LocatorUtils {
@@ -25,10 +26,7 @@ public class LocatorUtils {
}
private static String getByAttributeTextSelector(String attrName, Object value, boolean exact) {
if (value instanceof Pattern) {
return "internal:attr=[" + attrName + "=" + toJsRegExp((Pattern) value) + "]";
}
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector((String) value, exact) + "]";
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
}
static String getByTestIdSelector(Object testId) {
@@ -71,14 +69,7 @@ public class LocatorUtils {
if (options.level != null)
addAttr(result, "level", options.level.toString());
if (options.name != null) {
String name;
if (options.name instanceof String) {
name = escapeForAttributeSelector((String) options.name, options.exact != null && options.exact);
} else if (options.name instanceof Pattern) {
name = toJsRegExp((Pattern) options.name);
} else {
throw new IllegalArgumentException("options.name can be String or Pattern, found: " + options.name);
}
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
addAttr(result, "name", name);
}
if (options.pressed != null)
@@ -87,38 +78,33 @@ public class LocatorUtils {
return result.toString();
}
static String escapeForTextSelector(Object text, boolean exact) {
return escapeForTextSelector(text, exact, false);
private static String escapeRegexForSelector(Pattern re) {
// Even number of backslashes followed by the quote -> insert a backslash.
return toJsRegExp(re).replaceAll("(^|[^\\\\])(\\\\\\\\)*([\"'`])", "$1$2\\\\$3").replaceAll(">>", "\\\\>\\\\>");
}
private static String escapeForTextSelector(Object param, boolean exact, boolean caseSensitive) {
if (param instanceof Pattern) {
return toJsRegExp((Pattern) param);
static String escapeForTextSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (!(param instanceof String)) {
throw new IllegalArgumentException("text parameter must be Pattern or String: " + param);
if (value instanceof String) {
return gson().toJson(value) + (exact ? "s" : "i");
}
String text = (String) param;
if (exact) {
return '"' + text.replace("\"", "\\\"") + '"';
}
if (text.contains("\"") || text.contains(">>") || text.startsWith("/")) {
return "/" + escapeForRegex(text).replaceAll("\\s+", "\\\\s+") + "/" + (caseSensitive ? "" : "i");
}
return text;
throw new IllegalArgumentException("text parameter must be Pattern or String: " + value);
}
private static String escapeForRegex(String text) {
return text.replaceAll("[.*+?^>${}()|\\[\\]\\\\]", "\\\\\\\\$0");
}
private static String escapeForAttributeSelector(String value, boolean exact) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
private static String escapeForAttributeSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (value instanceof String) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + ((String) value).replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
}
throw new IllegalArgumentException("Attribute can be String or Pattern, found: " + value);
}
private static String toJsRegExp(Pattern pattern) {
@@ -171,6 +171,7 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
Router.HandleResult handled = routes.handle(route);
if (handled != Router.HandleResult.NoMatchingHandler) {
updateInterceptionPatterns();
@@ -182,16 +183,6 @@ public class PageImpl extends ChannelOwner implements Page {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
forceVideo().setArtifact(artifact);
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
listeners.notify(EventType.PAGEERROR, errorStr);
} else if ("crash".equals(event)) {
listeners.notify(EventType.CRASH, this);
} else if ("close".equals(event)) {
@@ -47,6 +47,10 @@ class SerializedValue{
Number h;
Integer id;
Integer ref;
// JS representation of Map: [[key1, value1], [key2, value2], ...].
SerializedValue m;
// JS representation of Set: [item1, item2, ...].
SerializedValue se;
}
class SerializedArgument{
@@ -78,7 +78,13 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public FrameImpl frame() {
return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
FrameImpl frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
if (frame.page == null) {
throw new PlaywrightException("Frame for this navigation request is not available, because the request\n" +
"was issued before the frame is created. You can check whether the request\n" +
"is a navigation request by calling isNavigationRequest() method.");
}
return frame;
}
@Override
@@ -32,7 +32,7 @@ import static com.microsoft.playwright.impl.Utils.convertType;
public class RouteImpl extends ChannelOwner implements Route {
private final RequestImpl request;
private boolean handled;
BrowserContextImpl browserContext;
boolean fallbackCalled;
boolean shouldResumeIfFallbackIsCalled;
@@ -99,7 +99,7 @@ public class RouteImpl extends ChannelOwner implements Route {
if (fetchOptions != null && fetchOptions.timeout != null) {
options.timeout = fetchOptions.timeout;
}
APIRequestContextImpl apiRequest = request.frame().page().context().request();
APIRequestContextImpl apiRequest = browserContext.request();
String url = (fetchOptions == null || fetchOptions.url == null) ? request().url() : fetchOptions.url;
return apiRequest.fetch(url, options);
}
@@ -280,6 +280,16 @@ class Serialization {
}
return (T) map;
}
if (value.m != null) {
Map<?, ?> map = new LinkedHashMap<>();
idToValue.put(value.id, map);
return (T) map;
}
if (value.se != null) {
Map<?, ?> map = new LinkedHashMap<>();
idToValue.put(value.id, map);
return (T) map;
}
throw new PlaywrightException("Unexpected result: " + gson().toJson(value));
}
@@ -174,10 +174,10 @@ class Utils {
return mimeType;
}
static final int maxUplodBufferSize = 50 * 1024 * 1024;
static final long maxUploadBufferSize = 50 * 1024 * 1024;
static boolean hasLargeFile(Path[] files) {
int totalSize = 0;
long totalSize = 0;
for (Path file: files) {
try {
totalSize += Files.size(file);
@@ -185,7 +185,7 @@ class Utils {
throw new PlaywrightException("Cannot get file size.", e);
}
}
return totalSize > maxUplodBufferSize;
return totalSize > maxUploadBufferSize;
}
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
@@ -216,11 +216,11 @@ class Utils {
}
static void checkFilePayloadSize(FilePayload[] files) {
int totalSize = 0;
long totalSize = 0;
for (FilePayload file: files) {
totalSize += file.buffer.length;
}
if (totalSize > maxUplodBufferSize) {
if (totalSize > maxUploadBufferSize) {
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
}
}
@@ -0,0 +1,23 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.WebError;
public class WebErrorImpl implements WebError {
private final PageImpl page;
private final String error;
WebErrorImpl(PageImpl page, String error) {
this.page = page;
this.error = error;
}
@Override
public PageImpl page() {
return page;
}
@Override
public String error() {
return error;
}
}
@@ -20,10 +20,17 @@ import org.junit.jupiter.api.*;
import com.microsoft.playwright.options.SameSiteAttribute;
import javax.sql.rowset.Predicate;
import java.io.IOException;
import java.security.Provider;
import java.time.Duration;
import java.time.Instant;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
import static com.microsoft.playwright.Utils.nextFreePort;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestBase {
@@ -150,6 +157,13 @@ public class TestBase {
}
}
void waitForCondition(BooleanSupplier predicate) {
waitForCondition(predicate, 5_000);
}
void waitForCondition(BooleanSupplier predicate, int timeoutMs) {
page.waitForCondition(predicate, new Page.WaitForConditionOptions().setTimeout(timeoutMs));
}
private static SameSiteAttribute initSameSiteAttribute() {
if (isChromium()) return SameSiteAttribute.LAX;
if (isWebKit()) return SameSiteAttribute.NONE;
@@ -7,6 +7,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestBrowserContextEvents extends TestBase {
@Test
@@ -156,4 +157,17 @@ public class TestBrowserContextEvents extends TestBase {
page.waitForCondition(() -> "hello".equals(popup.evaluate("window.result")),
new Page.WaitForConditionOptions().setTimeout(5_000));
}
@Test
void pageErrorEventShouldWork() {
WebError[] webError = { null };
context.onWebError(e -> {
webError[0] = e;
});
page.setContent("<script>throw new Error('boom')</script>");
waitForCondition(() -> webError[0] != null);
assertEquals(page, webError[0].page());
assertTrue(webError[0].error().contains("boom"), webError[0].error());
}
}
@@ -116,4 +116,11 @@ public class TestLocatorMisc extends TestBase{
assertEquals("outer", divLocator.locator("input").inputValue());
assertEquals("inner", page.frameLocator("iframe").locator(divLocator).locator("input").inputValue());
}
@Test
void shouldPressSequentially() {
page.setContent("<input type='text' />");
page.locator("input").pressSequentially("hello");
assertEquals("hello", page.evalOnSelector("input", "input => input.value"));
}
}
@@ -645,4 +645,14 @@ public class TestPageEvaluate extends TestBase {
assertTrue(object instanceof Date);
assertEquals(Date.from(instant), object);
}
@Test
void shouldTransferMaps() {
assertEquals(mapOf(), page.evaluate("() => new Map([[1, { test: 42n }]])"));
}
@Test
void shouldTransferSets() {
assertEquals(mapOf(), page.evaluate("() => new Set([1, { test: 42n }])"));
}
}
@@ -66,17 +66,18 @@ public class TestPageEventNetwork extends TestBase {
assertTrue(failedRequests.get(0).url().contains("one-style.css"));
assertNull(failedRequests.get(0).response());
assertEquals("stylesheet", failedRequests.get(0).resourceType());
String error = failedRequests.get(0).failure();
if (isChromium()) {
assertEquals("net::ERR_EMPTY_RESPONSE", failedRequests.get(0).failure());
assertEquals("net::ERR_EMPTY_RESPONSE", error);
} else if (isWebKit()) {
if (isMac)
assertEquals("The network connection was lost.", failedRequests.get(0).failure());
assertEquals("The network connection was lost.", error);
else if (isWindows)
assertEquals("Server returned nothing (no headers, no data)", failedRequests.get(0).failure());
assertEquals("Server returned nothing (no headers, no data)", error);
else
assertEquals("Message Corrupt", failedRequests.get(0).failure());
assertTrue("Message Corrupt".equals(error) || "Connection terminated unexpectedly".equals(error), error);
} else {
assertEquals("NS_ERROR_NET_RESET", failedRequests.get(0).failure());
assertEquals("NS_ERROR_NET_RESET", error);
}
assertNotNull(failedRequests.get(0).frame());
}
@@ -0,0 +1,17 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPageEventPopup extends TestBase {
@Test
void shouldWorkWithClickingTarget_blank() {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a target=_blank rel='opener' href='/one-style.html'>yo</a>");
Page popup = page.waitForPopup(() -> page.click("a"));
assertEquals(false, page.evaluate("() => !!window.opener"));
assertEquals(true, popup.evaluate("() => !!window.opener"));
assertEquals(popup, popup.mainFrame().page());
}
}
@@ -107,6 +107,39 @@ public class TestPageLocatorQuery extends TestBase {
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("Hello \"world\""))).textContent());
}
@Test
void shouldFilterByRegexWithASingleQuote() {
page.setContent("<button>let's let's<span>hello</span></button>");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let['abc]s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let['abc]s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let's let\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let's let\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\'s let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\'s let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
page.setContent("<button>let\\'s let\\'s<span>hello</span></button>");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\\\'s let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\\\'s let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
}
@Test
void shouldFilterByRegexAndRegexpFlags() {
page.setContent("<div>Hello \"world\"</div><div>Hello world</div>");
@@ -21,6 +21,8 @@ import com.microsoft.playwright.options.HttpHeader;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -113,4 +115,24 @@ public class TestPageNetworkRequest extends TestBase {
assertEquals("POST", request.method());
assertEquals(403, request.response().status());
}
@Test
void shouldNotAllowToAccessFrameOnPopupMainRequest() {
page.setContent("<a target=_blank href='" + server.EMPTY_PAGE + "'>click me</a>");
Request[] request = { null };
PlaywrightException[] error = { null };
page.context().onRequest(req -> {
request[0] = req;
try {
req.frame();
} catch (PlaywrightException e) {
error[0] = e;
}
});
page.getByText("click me").click();
waitForCondition(() -> request[0] != null);
assertTrue(request[0].isNavigationRequest());
assertTrue(error[0].getMessage().contains("Frame for this navigation request is not available"), error[0].getMessage());
}
}
@@ -0,0 +1,18 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestPageRequestIntercept extends TestBase {
@Test
void shouldFulfillPopupMainRequestUsingAlias() {
page.context().route("**/*", route -> {
APIResponse response = route.fetch();
route.fulfill(new Route.FulfillOptions().setResponse(response).setBody("hello" ));
});
page.setContent("<a target=_blank href='" + server.EMPTY_PAGE + "'>click me</a>");
Page popup = page.waitForPopup(() -> page.getByText("click me").click());
assertThat(popup.locator("body")).hasText("hello");
}
}
@@ -153,6 +153,25 @@ public class TestSelectorsGetBy extends TestBase {
assertThat(page.getByTitle("my title", new Page.GetByTitleOptions().setExact(true))).hasCount(1, new LocatorAssertions.HasCountOptions().setTimeout(500));
assertThat(page.getByTitle("my t\\itle", new Page.GetByTitleOptions().setExact(true))).hasCount(0, new LocatorAssertions.HasCountOptions().setTimeout(500));
assertThat(page.getByTitle("my t\\\\itle", new Page.GetByTitleOptions().setExact(true))).hasCount(0, new LocatorAssertions.HasCountOptions().setTimeout(500));
page.setContent("<label for=target>foo &gt;&gt; bar</label><input id=target>");
page.evalOnSelector("input", "input => {\n" +
" input.setAttribute('placeholder', 'foo >> bar');\n" +
" input.setAttribute('title', 'foo >> bar');\n" +
" input.setAttribute('alt', 'foo >> bar');\n" +
" }");
assertEquals("foo >> bar", page.getByText("foo >> bar").textContent());
assertThat(page.locator("label")).hasText("foo >> bar");
assertThat(page.getByText("foo >> bar")).hasText("foo >> bar");
assertEquals("foo >> bar", page.getByText(Pattern.compile("foo >> bar")).textContent());
assertThat(page.getByLabel("foo >> bar")).hasAttribute("id", "target");
assertThat(page.getByLabel(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
assertThat(page.getByPlaceholder("foo >> bar")).hasAttribute("id", "target");
assertThat(page.getByAltText("foo >> bar")).hasAttribute("id", "target");
assertThat(page.getByTitle("foo >> bar")).hasAttribute("id", "target");
assertThat(page.getByPlaceholder(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
assertThat(page.getByAltText(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
assertThat(page.getByTitle(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
}
@Test
@@ -9,6 +9,18 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestSelectorsText extends TestBase {
@Test
void shouldWorkSmoke() {
page.setContent("<div>Hi&gt;&gt;<span></span></div>");
assertEquals("<span></span>", page.evalOnSelector("text=\"Hi>>\">>span", "e => e.outerHTML"));
assertEquals("<span></span>", page.evalOnSelector("text=/Hi\\>\\>/ >> span", "e => e.outerHTML"));
page.setContent("<div>let's<span>hello</span></div>");
assertEquals("<span>hello</span>", page.evalOnSelector("text=/let's/i >> span", "e => e.outerHTML"));
assertEquals("<span>hello</span>", page.evalOnSelector("text=/let\'s/i >> span", "e => e.outerHTML"));
}
@Test
void hasTextAndInternalTextShouldMatchFullNodeTextInStrictMode() {
page.setContent("<div id=div1>hello<span>world</span></div>\n" +
+2 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@@ -44,6 +44,7 @@
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
<gson.version>2.8.9</gson.version>
<junit.version>5.7.0</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -1 +1 @@
1.37.0-beta-1691701681000
1.38.0
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -1156,13 +1156,18 @@ public class ApiGenerator {
File assertionsDir = new File(cwd,"playwright/src/main/java/com/microsoft/playwright/assertions");
System.out.println("Writing assertion files to: " + dir.getCanonicalPath());
generate(api, assertionsDir, "com.microsoft.playwright.assertions", isAssertion());
generate(api, assertionsDir, "com.microsoft.playwright.assertions", isAssertion().and(isSoftAssertion().negate()));
}
private static Predicate<String> isAssertion() {
return className -> className.toLowerCase().contains("assert");
}
// TODO: Remove this predicate once SoftAssertions are implemented.
private static Predicate<String> isSoftAssertion() {
return className -> className.contains("SoftAssertions");
}
private void generate(JsonArray api, File dir, String packageName, Predicate<String> classFilter) throws IOException {
Map<String, TypeDefinition> topLevelTypes = new HashMap<>();
for (JsonElement entry: api) {
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-fatjar</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Test Playwright Command Line FatJar</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.38.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on