Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c72ac5d3 |
@@ -25,12 +25,7 @@ extends:
|
||||
stages:
|
||||
- stage: Stage
|
||||
jobs:
|
||||
- job: Build
|
||||
templateContext:
|
||||
outputs:
|
||||
- output: pipelineArtifact
|
||||
path: $(Build.ArtifactStagingDirectory)/esrp-build
|
||||
artifact: esrp-build
|
||||
- job: HostJob
|
||||
steps:
|
||||
- bash: |
|
||||
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
|
||||
@@ -55,39 +50,28 @@ extends:
|
||||
- bash: ./scripts/download_driver.sh
|
||||
displayName: 'Download driver'
|
||||
|
||||
- bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(Build.ArtifactStagingDirectory)/esrp-build
|
||||
- bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(pwd)/local-build
|
||||
displayName: 'Build and deploy to a local directory'
|
||||
env:
|
||||
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
|
||||
|
||||
- job: Publish
|
||||
dependsOn: Build
|
||||
templateContext:
|
||||
type: releaseJob
|
||||
isProduction: true
|
||||
|
||||
- task: EsrpRelease@7
|
||||
inputs:
|
||||
- input: pipelineArtifact
|
||||
artifactName: esrp-build
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: EsrpRelease@9
|
||||
inputs:
|
||||
connectedservicename: 'Playwright-ESRP-PME'
|
||||
usemanagedidentity: true
|
||||
keyvaultname: 'playwright-esrp-pme'
|
||||
signcertname: 'ESRP-Release-Sign'
|
||||
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
|
||||
intent: 'PackageDistribution'
|
||||
contenttype: 'Maven'
|
||||
# Keeping it commented out as a workaround for:
|
||||
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
|
||||
# contentsource: 'folder'
|
||||
folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
|
||||
waitforreleasecompletion: true
|
||||
owners: 'yurys@microsoft.com'
|
||||
approvers: 'maxschmitt@microsoft.com'
|
||||
serviceendpointurl: 'https://api.esrp.microsoft.com'
|
||||
mainpublisher: 'Playwright'
|
||||
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
|
||||
displayName: 'ESRP Release to Maven'
|
||||
connectedservicename: 'Playwright-ESRP-Azure'
|
||||
keyvaultname: 'pw-publishing-secrets'
|
||||
authcertname: 'ESRP-Release-Auth'
|
||||
signcertname: 'ESRP-Release-Sign'
|
||||
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
|
||||
intent: 'PackageDistribution'
|
||||
contenttype: 'Maven'
|
||||
# Keeping it commented out as a workaround for:
|
||||
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
|
||||
# contentsource: 'folder'
|
||||
folderlocation: './local-build'
|
||||
waitforreleasecompletion: true
|
||||
owners: 'yurys@microsoft.com'
|
||||
approvers: 'maxschmitt@microsoft.com'
|
||||
serviceendpointurl: 'https://api.esrp.microsoft.com'
|
||||
mainpublisher: 'Playwright'
|
||||
domaintenantid: '72f988bf-86f1-41af-91ab-2d7cd011db47'
|
||||
displayName: 'ESRP Release to Maven'
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/java
|
||||
{
|
||||
"name": "Java",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/java:1-21-bookworm",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/java:1": {
|
||||
"version": "none",
|
||||
"installGradle": "false",
|
||||
"installMaven": "true"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
|
||||
}
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "java -version",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
@@ -17,11 +17,3 @@ updates:
|
||||
allow:
|
||||
- dependency-type: "direct" # Optional: Only update direct dependencies
|
||||
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
@@ -8,8 +8,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
env:
|
||||
PW_MAX_RETRIES: 3
|
||||
jobs:
|
||||
dev:
|
||||
timeout-minutes: 30
|
||||
@@ -20,9 +18,9 @@ jobs:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
@@ -65,13 +63,13 @@ jobs:
|
||||
browser-channel: msedge
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Media Pack
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: powershell
|
||||
run: Install-WindowsFeature Server-Media-Foundation
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
@@ -100,9 +98,9 @@ jobs:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: adopt
|
||||
java-version: 21
|
||||
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
matrix:
|
||||
flavor: [jammy, noble]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build Docker image
|
||||
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
|
||||
- name: Test
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download drivers
|
||||
run: scripts/download_driver.sh
|
||||
- name: Regenerate APIs
|
||||
|
||||
@@ -10,19 +10,59 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->134.0.6998.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->135.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->133.0.6943.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->134.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
## Documentation
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.
|
||||
|
||||
[https://playwright.dev/java/docs/intro](https://playwright.dev/java/docs/intro)
|
||||
* [Usage](#usage)
|
||||
- [Add Maven dependency](#add-maven-dependency)
|
||||
- [Is Playwright thread-safe?](#is-playwright-thread-safe)
|
||||
* [Examples](#examples)
|
||||
- [Page screenshot](#page-screenshot)
|
||||
- [Mobile and geolocation](#mobile-and-geolocation)
|
||||
- [Evaluate JavaScript in browser](#evaluate-javascript-in-browser)
|
||||
- [Intercept network requests](#intercept-network-requests)
|
||||
* [Documentation](#documentation)
|
||||
* [Contributing](#contributing)
|
||||
* [Is Playwright for Java ready?](#is-playwright-for-java-ready)
|
||||
|
||||
## API Reference
|
||||
## Usage
|
||||
|
||||
[https://playwright.dev/java/docs/api/class-playwright](https://playwright.dev/java/docs/api/class-playwright)
|
||||
Playwright requires **Java 8** or newer.
|
||||
|
||||
## Example
|
||||
#### Add Maven dependency
|
||||
|
||||
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add one dependency to your Maven `pom.xml` file as described below. If you're not familiar with Maven please refer to its [documentation](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
|
||||
|
||||
To run Playwright simply add following dependency to your Maven project:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.41.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
To run Playwright using Gradle add following dependency to your build.gradle file:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.41.0'
|
||||
}
|
||||
```
|
||||
|
||||
#### Is Playwright thread-safe?
|
||||
|
||||
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create multiple Playwright instances each on its own thread.
|
||||
|
||||
## Examples
|
||||
|
||||
You can find Maven project with the examples [here](./examples).
|
||||
|
||||
#### Page screenshot
|
||||
|
||||
This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots.
|
||||
|
||||
@@ -54,9 +94,100 @@ public class PageScreenshot {
|
||||
}
|
||||
```
|
||||
|
||||
## Other languages
|
||||
#### Mobile and geolocation
|
||||
|
||||
More comfortable in another programming language? [Playwright](https://playwright.dev) is also available in
|
||||
- [Node.js (JavaScript / TypeScript)](https://playwright.dev/docs/intro),
|
||||
- [Python](https://playwright.dev/python/docs/intro).
|
||||
- [.NET](https://playwright.dev/dotnet/docs/intro),
|
||||
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
|
||||
|
||||
```java
|
||||
import com.microsoft.playwright.options.*;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class MobileAndGeolocation {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch();
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
|
||||
.setViewportSize(411, 731)
|
||||
.setDeviceScaleFactor(2.625)
|
||||
.setIsMobile(true)
|
||||
.setHasTouch(true)
|
||||
.setLocale("en-US")
|
||||
.setGeolocation(41.889938, 12.492507)
|
||||
.setPermissions(asList("geolocation")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.openstreetmap.org/");
|
||||
page.click("a[data-bs-original-title=\"Show My Location\"]");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Evaluate JavaScript in browser
|
||||
|
||||
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
|
||||
|
||||
```java
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class EvaluateInBrowserContext {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.example.com/");
|
||||
Object dimensions = page.evaluate("() => {\n" +
|
||||
" return {\n" +
|
||||
" width: document.documentElement.clientWidth,\n" +
|
||||
" height: document.documentElement.clientHeight,\n" +
|
||||
" deviceScaleFactor: window.devicePixelRatio\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
System.out.println(dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Intercept network requests
|
||||
|
||||
This code snippet sets up request routing for a WebKit page to log all network requests.
|
||||
|
||||
```java
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class InterceptNetworkRequests {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.webkit().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.route("**", route -> {
|
||||
System.out.println(route.request().url());
|
||||
route.resume();
|
||||
});
|
||||
page.navigate("http://todomvc.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Check out our official [documentation site](https://playwright.dev/java).
|
||||
|
||||
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
|
||||
|
||||
## Contributing
|
||||
|
||||
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
|
||||
|
||||
## Is Playwright for Java ready?
|
||||
|
||||
Yes, Playwright for Java is ready. v1.10.0 is the first stable release. Going forward we will adhere to [semantic versioning](https://semver.org/) of the API.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
+2
-3
@@ -6,17 +6,16 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<playwright.version>1.51.0</playwright.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>${playwright.version}</version>
|
||||
<version>1.41.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
|
||||
@@ -59,10 +59,6 @@ public interface APIRequest {
|
||||
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
|
||||
*/
|
||||
public Map<String, String> extraHTTPHeaders;
|
||||
/**
|
||||
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
|
||||
*/
|
||||
public Boolean failOnStatusCode;
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
|
||||
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
|
||||
@@ -142,13 +138,6 @@ public interface APIRequest {
|
||||
this.extraHTTPHeaders = extraHTTPHeaders;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
|
||||
*/
|
||||
public NewContextOptions setFailOnStatusCode(boolean failOnStatusCode) {
|
||||
this.failOnStatusCode = failOnStatusCode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
|
||||
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
|
||||
|
||||
@@ -58,23 +58,12 @@ public interface APIRequestContext {
|
||||
}
|
||||
}
|
||||
class StorageStateOptions {
|
||||
/**
|
||||
* Set to {@code true} to include IndexedDB in the storage state snapshot.
|
||||
*/
|
||||
public Boolean indexedDB;
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
*/
|
||||
public Path path;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to include IndexedDB in the storage state snapshot.
|
||||
*/
|
||||
public StorageStateOptions setIndexedDB(boolean indexedDB) {
|
||||
this.indexedDB = indexedDB;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
|
||||
@@ -118,12 +118,6 @@ public interface Browser extends AutoCloseable {
|
||||
* "light"}.
|
||||
*/
|
||||
public Optional<ColorScheme> colorScheme;
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public Optional<Contrast> contrast;
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
@@ -341,15 +335,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.colorScheme = Optional.ofNullable(colorScheme);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public NewContextOptions setContrast(Contrast contrast) {
|
||||
this.contrast = Optional.ofNullable(contrast);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
@@ -686,12 +671,6 @@ public interface Browser extends AutoCloseable {
|
||||
* "light"}.
|
||||
*/
|
||||
public Optional<ColorScheme> colorScheme;
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public Optional<Contrast> contrast;
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
@@ -909,15 +888,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.colorScheme = Optional.ofNullable(colorScheme);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public NewPageOptions setContrast(Contrast contrast) {
|
||||
this.contrast = Optional.ofNullable(contrast);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
|
||||
@@ -407,31 +407,12 @@ public interface BrowserContext extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
class StorageStateOptions {
|
||||
/**
|
||||
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
|
||||
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
|
||||
* Authentication, enable this.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> IndexedDBs with typed arrays are currently not supported.
|
||||
*/
|
||||
public Boolean indexedDB;
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
*/
|
||||
public Path path;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
|
||||
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
|
||||
* Authentication, enable this.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> IndexedDBs with typed arrays are currently not supported.
|
||||
*/
|
||||
public StorageStateOptions setIndexedDB(boolean indexedDB) {
|
||||
this.indexedDB = indexedDB;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
@@ -1454,7 +1435,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
void setOffline(boolean offline);
|
||||
/**
|
||||
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
||||
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
|
||||
*
|
||||
* @since v1.8
|
||||
*/
|
||||
@@ -1462,7 +1443,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
return storageState(null);
|
||||
}
|
||||
/**
|
||||
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
||||
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
|
||||
*
|
||||
* @since v1.8
|
||||
*/
|
||||
|
||||
@@ -497,12 +497,6 @@ public interface BrowserType {
|
||||
* "light"}.
|
||||
*/
|
||||
public Optional<ColorScheme> colorScheme;
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public Optional<Contrast> contrast;
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
@@ -822,15 +816,6 @@ public interface BrowserType {
|
||||
this.colorScheme = Optional.ofNullable(colorScheme);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
|
||||
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
|
||||
* emulation to system defaults. Defaults to {@code "no-preference"}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setContrast(Contrast contrast) {
|
||||
this.contrast = Optional.ofNullable(contrast);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
|
||||
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
|
||||
@@ -1212,24 +1197,22 @@ public interface BrowserType {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
|
||||
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
|
||||
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
|
||||
* compatible with 1.2.x).
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
|
||||
* launches the browser (1.2.3 → is compatible with 1.2.x).
|
||||
*
|
||||
* @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
* @since v1.8
|
||||
*/
|
||||
default Browser connect(String wsEndpoint) {
|
||||
return connect(wsEndpoint, null);
|
||||
}
|
||||
/**
|
||||
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
|
||||
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
|
||||
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
|
||||
* compatible with 1.2.x).
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
|
||||
* launches the browser (1.2.3 → is compatible with 1.2.x).
|
||||
*
|
||||
* @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
* @since v1.8
|
||||
*/
|
||||
Browser connect(String wsEndpoint, ConnectOptions options);
|
||||
@@ -1240,11 +1223,6 @@ public interface BrowserType {
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
|
||||
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
|
||||
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
|
||||
* BrowserType.connect()}.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
|
||||
@@ -1266,11 +1244,6 @@ public interface BrowserType {
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
|
||||
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
|
||||
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
|
||||
* BrowserType.connect()}.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
|
||||
|
||||
@@ -599,9 +599,7 @@ public interface ElementHandle extends JSHandle {
|
||||
public ScreenshotCaret caret;
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public List<Locator> mask;
|
||||
/**
|
||||
@@ -675,9 +673,7 @@ public interface ElementHandle extends JSHandle {
|
||||
}
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public ScreenshotOptions setMask(List<Locator> mask) {
|
||||
this.mask = mask;
|
||||
|
||||
@@ -715,10 +715,6 @@ public interface Locator {
|
||||
* <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public Object hasText;
|
||||
/**
|
||||
* Only matches visible or invisible elements.
|
||||
*/
|
||||
public Boolean visible;
|
||||
|
||||
/**
|
||||
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
|
||||
@@ -781,13 +777,6 @@ public interface Locator {
|
||||
this.hasText = hasText;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Only matches visible or invisible elements.
|
||||
*/
|
||||
public FilterOptions setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FocusOptions {
|
||||
/**
|
||||
@@ -1530,9 +1519,7 @@ public interface Locator {
|
||||
public ScreenshotCaret caret;
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public List<Locator> mask;
|
||||
/**
|
||||
@@ -1606,9 +1593,7 @@ public interface Locator {
|
||||
}
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public ScreenshotOptions setMask(List<Locator> mask) {
|
||||
this.mask = mask;
|
||||
@@ -2855,6 +2840,10 @@ public interface Locator {
|
||||
* <p> If {@code expression} throws or rejects, this method throws.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* Locator tweets = page.locator(".tweet .retweets");
|
||||
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
|
||||
* }</pre>
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
@@ -2879,6 +2868,10 @@ public interface Locator {
|
||||
* <p> If {@code expression} throws or rejects, this method throws.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* Locator tweets = page.locator(".tweet .retweets");
|
||||
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
|
||||
* }</pre>
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
@@ -2902,6 +2895,10 @@ public interface Locator {
|
||||
* <p> If {@code expression} throws or rejects, this method throws.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* Locator tweets = page.locator(".tweet .retweets");
|
||||
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
|
||||
* }</pre>
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
@@ -5215,9 +5212,7 @@ public interface Locator {
|
||||
*/
|
||||
void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
|
||||
/**
|
||||
* Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
|
||||
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
|
||||
* events</a> page.
|
||||
* Perform a tap gesture on the element matching the locator.
|
||||
*
|
||||
* <p> <strong>Details</strong>
|
||||
*
|
||||
@@ -5243,9 +5238,7 @@ public interface Locator {
|
||||
tap(null);
|
||||
}
|
||||
/**
|
||||
* Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
|
||||
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
|
||||
* events</a> page.
|
||||
* Perform a tap gesture on the element matching the locator.
|
||||
*
|
||||
* <p> <strong>Details</strong>
|
||||
*
|
||||
|
||||
@@ -965,11 +965,6 @@ public interface Page extends AutoCloseable {
|
||||
* {@code "no-preference"} is deprecated.
|
||||
*/
|
||||
public Optional<ColorScheme> colorScheme;
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing
|
||||
* {@code null} disables contrast emulation.
|
||||
*/
|
||||
public Optional<Contrast> contrast;
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code
|
||||
* null} disables forced colors emulation.
|
||||
@@ -996,14 +991,6 @@ public interface Page extends AutoCloseable {
|
||||
this.colorScheme = Optional.ofNullable(colorScheme);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing
|
||||
* {@code null} disables contrast emulation.
|
||||
*/
|
||||
public EmulateMediaOptions setContrast(Contrast contrast) {
|
||||
this.contrast = Optional.ofNullable(contrast);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code
|
||||
* null} disables forced colors emulation.
|
||||
@@ -2535,9 +2522,7 @@ public interface Page extends AutoCloseable {
|
||||
public Boolean fullPage;
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public List<Locator> mask;
|
||||
/**
|
||||
@@ -2632,9 +2617,7 @@ public interface Page extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
|
||||
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
|
||||
* visible elements</a> to disable that.
|
||||
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
|
||||
*/
|
||||
public ScreenshotOptions setMask(List<Locator> mask) {
|
||||
this.mask = mask;
|
||||
|
||||
@@ -20,9 +20,6 @@ package com.microsoft.playwright;
|
||||
/**
|
||||
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
|
||||
* touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true.
|
||||
*
|
||||
* <p> This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch
|
||||
* events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch events</a> page.
|
||||
*/
|
||||
public interface Touchscreen {
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The {@code WebSocket} class represents WebSocket connections within a page. It provides the ability to inspect and
|
||||
* manipulate the data being transmitted and received.
|
||||
*
|
||||
* <p> If you want to intercept or modify WebSocket frames, consider using {@code WebSocketRoute}.
|
||||
* The {@code WebSocket} class represents websocket connections in the page.
|
||||
*/
|
||||
public interface WebSocket {
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public interface PageAssertions {
|
||||
class HasURLOptions {
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression parameter if specified. A provided predicate ignores this flag.
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public Boolean ignoreCase;
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ public interface PageAssertions {
|
||||
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression parameter if specified. A provided predicate ignores this flag.
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public HasURLOptions setIgnoreCase(boolean ignoreCase) {
|
||||
this.ignoreCase = ignoreCase;
|
||||
|
||||
@@ -46,20 +46,22 @@ class APIResponseImpl implements APIResponse {
|
||||
|
||||
@Override
|
||||
public byte[] body() {
|
||||
try {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
|
||||
if (!json.has("binary")) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
return context.withLogging("APIResponse.body", () -> {
|
||||
try {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
|
||||
if (!json.has("binary")) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
return Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
} catch (PlaywrightException e) {
|
||||
if (isSafeCloseError(e)) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
} catch (PlaywrightException e) {
|
||||
if (isSafeCloseError(e)) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -623,22 +623,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public String storageState(StorageStateOptions options) {
|
||||
return withLogging("BrowserContext.storageState", () -> storageStateImpl(options));
|
||||
}
|
||||
|
||||
private String storageStateImpl(StorageStateOptions options) {
|
||||
if (options == null) {
|
||||
options = new StorageStateOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("path");
|
||||
JsonElement json = sendMessage("storageState", params);
|
||||
|
||||
String storageState = json.toString();
|
||||
if (options.path != null) {
|
||||
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
|
||||
}
|
||||
return storageState;
|
||||
return withLogging("BrowserContext.storageState", () -> {
|
||||
JsonElement json = sendMessage("storageState");
|
||||
String storageState = json.toString();
|
||||
if (options != null && options.path != null) {
|
||||
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
|
||||
}
|
||||
return storageState;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.microsoft.playwright.options.HarContentPolicy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
@@ -197,10 +196,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
|
||||
Path cwd = Paths.get("").toAbsolutePath();
|
||||
userDataDir = cwd.resolve(userDataDir);
|
||||
}
|
||||
params.addProperty("userDataDir", userDataDir.toString());
|
||||
if (recordHar != null) {
|
||||
params.add("recordHar", recordHar);
|
||||
|
||||
@@ -136,6 +136,6 @@ class FrameLocatorImpl implements FrameLocator {
|
||||
|
||||
@Override
|
||||
public Locator owner() {
|
||||
return new LocatorImpl(frame, frameSelector, null);
|
||||
return new LocatorImpl(frame, frameSelector);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,29 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.LocatorUtils.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
|
||||
class LocatorImpl implements Locator {
|
||||
final FrameImpl frame;
|
||||
final String selector;
|
||||
|
||||
LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
|
||||
this(frame, selector, options, null);
|
||||
LocatorImpl(FrameImpl frame, String frameSelector) {
|
||||
this(frame, frameSelector, null);
|
||||
}
|
||||
|
||||
private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Boolean visible) {
|
||||
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
|
||||
this.frame = frame;
|
||||
if (options != null) {
|
||||
if (options.hasText != null) {
|
||||
@@ -61,9 +65,6 @@ class LocatorImpl implements Locator {
|
||||
selector += " >> internal:has-not=" + gson().toJson(locator.selector);
|
||||
}
|
||||
}
|
||||
if (visible != null) {
|
||||
selector += " >> visible=" + visible;
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
@@ -251,8 +252,7 @@ class LocatorImpl implements Locator {
|
||||
|
||||
@Override
|
||||
public Locator filter(FilterOptions options) {
|
||||
Boolean visible = (options == null) ? null : options.visible;
|
||||
return new LocatorImpl(frame, selector, convertType(options, LocatorOptions.class), visible);
|
||||
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,7 +47,6 @@ class Serialization {
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
|
||||
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
|
||||
.registerTypeAdapter(Contrast.class, new ToLowerCaseAndDashSerializer<Contrast>())
|
||||
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
|
||||
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
|
||||
.registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer<HttpCredentialsSend>())
|
||||
@@ -426,7 +425,6 @@ class Serialization {
|
||||
private static boolean isSupported(Type type) {
|
||||
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<Contrast>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
|
||||
|
||||
@@ -434,7 +434,7 @@ public class Utils {
|
||||
}
|
||||
String pfxBase64 = base64Buffer(cert.pfx, cert.pfxPath);
|
||||
if (pfxBase64 != null) {
|
||||
jsonCert.addProperty("pfx", pfxBase64);
|
||||
params.addProperty("pfx", pfxBase64);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file", e);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 enum Contrast {
|
||||
NO_PREFERENCE,
|
||||
MORE
|
||||
}
|
||||
@@ -22,9 +22,8 @@ import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.text.ParseException;
|
||||
@@ -35,7 +34,7 @@ import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static com.microsoft.playwright.Utils.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.Utils.assertJsonEquals;
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestBrowserContextStorageState extends TestBase {
|
||||
@@ -170,97 +169,4 @@ public class TestBrowserContextStorageState extends TestBase {
|
||||
" }]\n" +
|
||||
"}]}", new Gson().fromJson(storageState, JsonObject.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportIndexedDB() {
|
||||
page.navigate(server.PREFIX + "/to-do-notifications/index.html");
|
||||
page.locator("label:has-text('Task title')").fill("Pet the cat");
|
||||
page.locator("label:has-text('Hours')").fill("1");
|
||||
page.locator("label:has-text('Mins')").fill("1");
|
||||
page.locator("text=Add Task").click();
|
||||
|
||||
String storageState = page.context().storageState(new BrowserContext.StorageStateOptions().setIndexedDB(true));
|
||||
assertJsonEquals("{\"cookies\":[],\"origins\":[\n" +
|
||||
" {\n" +
|
||||
" \"origin\": \"" + server.PREFIX + "\",\n" +
|
||||
" \"localStorage\": [],\n" +
|
||||
" \"indexedDB\": [\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"toDoList\",\n" +
|
||||
" \"version\": 4,\n" +
|
||||
" \"stores\": [\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"toDoList\",\n" +
|
||||
" \"autoIncrement\": false,\n" +
|
||||
" \"keyPath\": \"taskTitle\",\n" +
|
||||
" \"records\": [\n" +
|
||||
" {\n" +
|
||||
" \"value\": {\n" +
|
||||
" \"day\": \"01\",\n" +
|
||||
" \"hours\": \"1\",\n" +
|
||||
" \"minutes\": \"1\",\n" +
|
||||
" \"month\": \"January\",\n" +
|
||||
" \"notified\": \"no\",\n" +
|
||||
" \"taskTitle\": \"Pet the cat\",\n" +
|
||||
" \"year\": \"2025\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"indexes\": [\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"day\",\n" +
|
||||
" \"keyPath\": \"day\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"hours\",\n" +
|
||||
" \"keyPath\": \"hours\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"minutes\",\n" +
|
||||
" \"keyPath\": \"minutes\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"month\",\n" +
|
||||
" \"keyPath\": \"month\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"notified\",\n" +
|
||||
" \"keyPath\": \"notified\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\": \"year\",\n" +
|
||||
" \"keyPath\": \"year\",\n" +
|
||||
" \"multiEntry\": false,\n" +
|
||||
" \"unique\": false\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
"]}", new Gson().fromJson(storageState, JsonObject.class));
|
||||
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setStorageState(storageState));
|
||||
assertEquals(storageState, context.storageState(new BrowserContext.StorageStateOptions().setIndexedDB(true)));
|
||||
|
||||
Page recreatedPage = context.newPage();
|
||||
recreatedPage.navigate(server.PREFIX + "/to-do-notifications/index.html");
|
||||
assertThat(recreatedPage.locator("#task-list")).matchesAriaSnapshot("\n" +
|
||||
" - list:\n" +
|
||||
" - listitem:\n" +
|
||||
" - text: /Pet the cat/\n");
|
||||
assertEquals("{\"cookies\":[],\"origins\":[]}", context.storageState(
|
||||
new BrowserContext.StorageStateOptions().setIndexedDB(false)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.ClientCertificate;
|
||||
import com.microsoft.playwright.options.Proxy;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -119,25 +120,6 @@ public class TestClientCertificates extends TestBase {
|
||||
request.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passWithTrustedClientCertificatesPfx() {
|
||||
APIRequest.NewContextOptions requestOptions = new APIRequest.NewContextOptions()
|
||||
.setIgnoreHTTPSErrors(true) // TODO: remove once we can pass a custom CA.
|
||||
.setClientCertificates(asList(
|
||||
new ClientCertificate(customServer.origin)
|
||||
.setPfxPath(asset("client-certificates/client/trusted/client_keystore.p12"))
|
||||
.setPassphrase("passphrase")));
|
||||
|
||||
APIRequestContext request = playwright.request().newContext(requestOptions);
|
||||
APIResponse response = request.get(customServer.url);
|
||||
|
||||
assertEquals(customServer.url, response.url());
|
||||
assertEquals(200, response.status());
|
||||
assertTrue(response.text().contains("Hello CN=Alice, your certificate was issued by O=Client Certificate Demo,CN=localhost!"), response.text());
|
||||
|
||||
request.dispose();
|
||||
}
|
||||
|
||||
static boolean isWebKitMacOS() {
|
||||
return isWebKit() && isMac;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.Contrast;
|
||||
import com.microsoft.playwright.options.Geolocation;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
@@ -29,7 +28,6 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -284,27 +282,4 @@ public class TestDefaultBrowserContext2 extends TestBase {
|
||||
assertEquals(1, fields.size());
|
||||
assertEquals("200MB.zip", fields.get(0).filename);
|
||||
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportContrastOption() {
|
||||
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().setContrast(Contrast.MORE));
|
||||
assertEquals(true, page.evaluate("() => matchMedia('(prefers-contrast: more)').matches"));
|
||||
assertEquals(false, page.evaluate("() => matchMedia('(prefers-contrast: no-preference)').matches"));
|
||||
}
|
||||
|
||||
static boolean tempDirCanBeOnDifferentRoot() {
|
||||
return isWindows;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="tempDirCanBeOnDifferentRoot", disabledReason="IllegalArgument 'other' has different root on GitHub Actions.")
|
||||
void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception {
|
||||
Path userDataDir = tempDir.resolve("user-data-dir");
|
||||
Path cwd = Paths.get("").toAbsolutePath();
|
||||
Path relativePath = cwd.relativize(userDataDir);
|
||||
BrowserContext context = browserType.launchPersistentContext(relativePath);
|
||||
assertTrue(Files.list(userDataDir).count() > 0);
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -548,36 +548,4 @@ public class TestGlobalFetch extends TestBase {
|
||||
assertEquals(4, requestCount[0]);
|
||||
requestContext.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowWhenFailOnStatusCodeIsSetToTrue() {
|
||||
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setFailOnStatusCode(true));
|
||||
server.setRoute("/empty.html", exchange -> {
|
||||
exchange.getResponseHeaders().set("Content-Length", "10");
|
||||
exchange.getResponseHeaders().set("Content-type", "text/plain");
|
||||
exchange.sendResponseHeaders(404, 10);
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("Not found.");
|
||||
}
|
||||
});
|
||||
PlaywrightException error = assertThrows(PlaywrightException.class, () -> {
|
||||
request.get(server.EMPTY_PAGE);
|
||||
});
|
||||
assertTrue(error.getMessage().contains("404 Not Found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotThrowWhenFailOnStatusCodeIsSetToFalse() {
|
||||
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setFailOnStatusCode(false));
|
||||
server.setRoute("/empty.html", exchange -> {
|
||||
exchange.getResponseHeaders().set("Content-Length", "10");
|
||||
exchange.getResponseHeaders().set("Content-type", "text/plain");
|
||||
exchange.sendResponseHeaders(404, 10);
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("Not found.");
|
||||
}
|
||||
});
|
||||
APIResponse response = request.get(server.EMPTY_PAGE);
|
||||
assertEquals(404, response.status());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestLocatorMisc extends TestBase{
|
||||
@@ -124,20 +123,4 @@ public class TestLocatorMisc extends TestBase{
|
||||
page.locator("input").pressSequentially("hello");
|
||||
assertEquals("hello", page.evalOnSelector("input", "input => input.value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportFilterVisible() {
|
||||
page.setContent("<div>\n" +
|
||||
" <div class=\"item\" style=\"display: none\">Hidden data0</div>\n" +
|
||||
" <div class=\"item\">visible data1</div>\n" +
|
||||
" <div class=\"item\" style=\"display: none\">Hidden data1</div>\n" +
|
||||
" <div class=\"item\">visible data2</div>\n" +
|
||||
" <div class=\"item\" style=\"display: none\">Hidden data2</div>\n" +
|
||||
" <div class=\"item\">visible data3</div>\n" +
|
||||
"</div>");
|
||||
Locator locator = page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).nth(1);
|
||||
assertThat(locator).hasText("visible data2");
|
||||
assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).getByText("data3")).hasText("visible data3");
|
||||
assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(false)).getByText("data1")).hasText("Hidden data1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.Contrast;
|
||||
import com.microsoft.playwright.options.ForcedColors;
|
||||
import com.microsoft.playwright.options.ReducedMotion;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -172,17 +171,4 @@ public class TestPageEmulateMedia extends TestBase {
|
||||
page.emulateMedia(new Page.EmulateMediaOptions().setForcedColors(null));
|
||||
assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: none)').matches"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEmulateContrast() {
|
||||
assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches"));
|
||||
page.emulateMedia(new Page.EmulateMediaOptions().setContrast(Contrast.NO_PREFERENCE));
|
||||
assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches"));
|
||||
assertEquals(false, page.evaluate("matchMedia('(prefers-contrast: more)').matches"));
|
||||
page.emulateMedia(new Page.EmulateMediaOptions().setContrast(Contrast.MORE));
|
||||
assertEquals(false, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches"));
|
||||
assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: more)').matches"));
|
||||
page.emulateMedia(new Page.EmulateMediaOptions().setContrast(null));
|
||||
assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public class TestPageScreenshot extends TestBase {
|
||||
}
|
||||
|
||||
static boolean isScreenshotTestDisabled() {
|
||||
if (isWebKit() || isChromium()) {
|
||||
if (isWebKit()) {
|
||||
// Array lengths differ.
|
||||
return true;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,116 +0,0 @@
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||
@@ -1 +0,0 @@
|
||||
Source: https://github.com/mdn/dom-examples/tree/main/to-do-notifications
|
||||
@@ -1,108 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=380">
|
||||
<script src="scripts/todo.js"></script>
|
||||
<title>To-do list with Notifications</title>
|
||||
<link href="style/style.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>To-do list</h1>
|
||||
|
||||
<div class="task-box">
|
||||
|
||||
<ul id="task-list">
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-box">
|
||||
<h2>Add new to-do item.</h2>
|
||||
|
||||
<form id="task-form" action="index.html">
|
||||
<div class="full-width"><label for="title">Task title:</label><input type="text" id="title" required></div>
|
||||
<div class="half-width"><label for="deadline-hours">Hours (hh):</label><input type="number" id="deadline-hours" required></div>
|
||||
<div class="half-width"><label for="deadline-minutes">Mins (mm):</label><input type="number" id="deadline-minutes" required></div>
|
||||
<div class="third-width"><label for="deadline-day">Day:</label>
|
||||
<select id="deadline-day" required>
|
||||
<option value="01">01</option>
|
||||
<option value="02">02</option>
|
||||
<option value="03">03</option>
|
||||
<option value="04">04</option>
|
||||
<option value="05">05</option>
|
||||
<option value="06">06</option>
|
||||
<option value="07">07</option>
|
||||
<option value="08">08</option>
|
||||
<option value="09">09</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</select></div>
|
||||
|
||||
<div class="third-width"><label for="deadline-month">Month:</label>
|
||||
<select id="deadline-month" required>
|
||||
<option value="January">January</option>
|
||||
<option value="February">February</option>
|
||||
<option value="March">March</option>
|
||||
<option value="April">April</option>
|
||||
<option value="May">May</option>
|
||||
<option value="June">June</option>
|
||||
<option value="July">July</option>
|
||||
<option value="August">August</option>
|
||||
<option value="September">September</option>
|
||||
<option value="October">October</option>
|
||||
<option value="November">November</option>
|
||||
<option value="December">December</option>
|
||||
</select></div>
|
||||
|
||||
<div class="third-width"><label for="deadline-year">Year:</label>
|
||||
<select id="deadline-year" required>
|
||||
<option value="2025">2025</option>
|
||||
<option value="2024">2024</option>
|
||||
<option value="2023">2023</option>
|
||||
<option value="2022">2022</option>
|
||||
<option value="2021">2021</option>
|
||||
<option value="2020">2020</option>
|
||||
<option value="2019">2019</option>
|
||||
<option value="2018">2018</option>
|
||||
</select></div>
|
||||
|
||||
<div><input type="submit" id="submit" value="Add Task"></div>
|
||||
<div></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="toolbar">
|
||||
<ul id="notifications">
|
||||
|
||||
</ul>
|
||||
|
||||
<button id="enable">
|
||||
Enable notifications
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"version": "0.1",
|
||||
"name": "To-do list",
|
||||
"description": "Store to-do items on your device, and be notified when the deadlines are up.",
|
||||
"launch_path": "/to-do-notifications/index.html",
|
||||
"icons": {
|
||||
"128": "/to-do-notifications/img/icon-128.png"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Chris Mills",
|
||||
"url": "http://chrisdavidmills.github.io/to-do-notifications/"
|
||||
},
|
||||
"permissions": {
|
||||
"desktop-notification": {
|
||||
"description": "Needed for creating system notifications."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
window.onload = () => {
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
// Hold an instance of a db object for us to store the IndexedDB data in
|
||||
let db;
|
||||
|
||||
// Create a reference to the notifications list in the bottom of the app; we will write database messages into this list by
|
||||
// appending list items as children of this element
|
||||
const note = document.getElementById('notifications');
|
||||
|
||||
// All other UI elements we need for the app
|
||||
const taskList = document.getElementById('task-list');
|
||||
const taskForm = document.getElementById('task-form');
|
||||
const title = document.getElementById('title');
|
||||
const hours = document.getElementById('deadline-hours');
|
||||
const minutes = document.getElementById('deadline-minutes');
|
||||
const day = document.getElementById('deadline-day');
|
||||
const month = document.getElementById('deadline-month');
|
||||
const year = document.getElementById('deadline-year');
|
||||
const notificationBtn = document.getElementById('enable');
|
||||
|
||||
// Do an initial check to see what the notification permission state is
|
||||
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||
notificationBtn.style.display = 'block';
|
||||
} else {
|
||||
notificationBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
note.appendChild(createListItem('App initialised.'));
|
||||
|
||||
// Let us open our database
|
||||
const DBOpenRequest = window.indexedDB.open('toDoList', 4);
|
||||
|
||||
// Register two event handlers to act on the database being opened successfully, or not
|
||||
DBOpenRequest.onerror = (event) => {
|
||||
note.appendChild(createListItem('Error loading database.'));
|
||||
};
|
||||
|
||||
DBOpenRequest.onsuccess = (event) => {
|
||||
note.appendChild(createListItem('Database initialised.'));
|
||||
|
||||
// Store the result of opening the database in the db variable. This is used a lot below
|
||||
db = DBOpenRequest.result;
|
||||
|
||||
// Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB
|
||||
displayData();
|
||||
};
|
||||
|
||||
// This event handles the event whereby a new version of the database needs to be created
|
||||
// Either one has not been created before, or a new version number has been submitted via the
|
||||
// window.indexedDB.open line above
|
||||
//it is only implemented in recent browsers
|
||||
DBOpenRequest.onupgradeneeded = (event) => {
|
||||
db = event.target.result;
|
||||
|
||||
db.onerror = (event) => {
|
||||
note.appendChild(createListItem('Error loading database.'));
|
||||
};
|
||||
|
||||
// Create an objectStore for this database
|
||||
const objectStore = db.createObjectStore('toDoList', { keyPath: 'taskTitle' });
|
||||
|
||||
// Define what data items the objectStore will contain
|
||||
objectStore.createIndex('hours', 'hours', { unique: false });
|
||||
objectStore.createIndex('minutes', 'minutes', { unique: false });
|
||||
objectStore.createIndex('day', 'day', { unique: false });
|
||||
objectStore.createIndex('month', 'month', { unique: false });
|
||||
objectStore.createIndex('year', 'year', { unique: false });
|
||||
|
||||
objectStore.createIndex('notified', 'notified', { unique: false });
|
||||
|
||||
note.appendChild(createListItem('Object store created.'));
|
||||
};
|
||||
|
||||
function displayData() {
|
||||
// First clear the content of the task list so that you don't get a huge long list of duplicate stuff each time
|
||||
// the display is updated.
|
||||
while (taskList.firstChild) {
|
||||
taskList.removeChild(taskList.lastChild);
|
||||
}
|
||||
|
||||
// Open our object store and then get a cursor list of all the different data items in the IDB to iterate through
|
||||
const objectStore = db.transaction('toDoList').objectStore('toDoList');
|
||||
objectStore.openCursor().onsuccess = (event) => {
|
||||
const cursor = event.target.result;
|
||||
// Check if there are no (more) cursor items to iterate through
|
||||
if (!cursor) {
|
||||
// No more items to iterate through, we quit.
|
||||
note.appendChild(createListItem('Entries all displayed.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check which suffix the deadline day of the month needs
|
||||
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
|
||||
const ordDay = ordinal(day);
|
||||
|
||||
// Build the to-do list entry and put it into the list item.
|
||||
const toDoText = `${taskTitle} — ${hours}:${minutes}, ${month} ${ordDay} ${year}.`;
|
||||
const listItem = createListItem(toDoText);
|
||||
|
||||
if (notified === 'yes') {
|
||||
listItem.style.textDecoration = 'line-through';
|
||||
listItem.style.color = 'rgba(255, 0, 0, 0.5)';
|
||||
}
|
||||
|
||||
// Put the item item inside the task list
|
||||
taskList.appendChild(listItem);
|
||||
|
||||
// Create a delete button inside each list item,
|
||||
const deleteButton = document.createElement('button');
|
||||
listItem.appendChild(deleteButton);
|
||||
deleteButton.textContent = 'X';
|
||||
|
||||
// Set a data attribute on our delete button to associate the task it relates to.
|
||||
deleteButton.setAttribute('data-task', taskTitle);
|
||||
|
||||
// Associate action (deletion) when clicked
|
||||
deleteButton.onclick = (event) => {
|
||||
deleteItem(event);
|
||||
};
|
||||
|
||||
// continue on to the next item in the cursor
|
||||
cursor.continue();
|
||||
};
|
||||
};
|
||||
|
||||
// Add listener for clicking the submit button
|
||||
taskForm.addEventListener('submit', addData, false);
|
||||
|
||||
function addData(e) {
|
||||
// Prevent default, as we don't want the form to submit in the conventional way
|
||||
e.preventDefault();
|
||||
|
||||
// Stop the form submitting if any values are left empty.
|
||||
// This should never happen as there is the required attribute
|
||||
if (title.value === '' || hours.value === null || minutes.value === null || day.value === '' || month.value === '' || year.value === null) {
|
||||
note.appendChild(createListItem('Data not submitted — form incomplete.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the values entered into the form fields and store them in an object ready for being inserted into the IndexedDB
|
||||
const newItem = [
|
||||
{ taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: 'no' },
|
||||
];
|
||||
|
||||
// Open a read/write DB transaction, ready for adding the data
|
||||
const transaction = db.transaction(['toDoList'], 'readwrite');
|
||||
|
||||
// Report on the success of the transaction completing, when everything is done
|
||||
transaction.oncomplete = () => {
|
||||
note.appendChild(createListItem('Transaction completed: database modification finished.'));
|
||||
|
||||
// Update the display of data to show the newly added item, by running displayData() again.
|
||||
displayData();
|
||||
};
|
||||
|
||||
// Handler for any unexpected error
|
||||
transaction.onerror = () => {
|
||||
note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`));
|
||||
};
|
||||
|
||||
// Call an object store that's already been added to the database
|
||||
const objectStore = transaction.objectStore('toDoList');
|
||||
console.log(objectStore.indexNames);
|
||||
console.log(objectStore.keyPath);
|
||||
console.log(objectStore.name);
|
||||
console.log(objectStore.transaction);
|
||||
console.log(objectStore.autoIncrement);
|
||||
|
||||
// Make a request to add our newItem object to the object store
|
||||
const objectStoreRequest = objectStore.add(newItem[0]);
|
||||
objectStoreRequest.onsuccess = (event) => {
|
||||
|
||||
// Report the success of our request
|
||||
// (to detect whether it has been succesfully
|
||||
// added to the database, you'd look at transaction.oncomplete)
|
||||
note.appendChild(createListItem('Request successful.'));
|
||||
|
||||
// Clear the form, ready for adding the next entry
|
||||
title.value = '';
|
||||
hours.value = null;
|
||||
minutes.value = null;
|
||||
day.value = 01;
|
||||
month.value = 'January';
|
||||
year.value = 2020;
|
||||
};
|
||||
};
|
||||
|
||||
function deleteItem(event) {
|
||||
// Retrieve the name of the task we want to delete
|
||||
const dataTask = event.target.getAttribute('data-task');
|
||||
|
||||
// Open a database transaction and delete the task, finding it by the name we retrieved above
|
||||
const transaction = db.transaction(['toDoList'], 'readwrite');
|
||||
transaction.objectStore('toDoList').delete(dataTask);
|
||||
|
||||
// Report that the data item has been deleted
|
||||
transaction.oncomplete = () => {
|
||||
// Delete the parent of the button, which is the list item, so it is no longer displayed
|
||||
event.target.parentNode.parentNode.removeChild(event.target.parentNode);
|
||||
note.appendChild(createListItem(`Task "${dataTask}" deleted.`));
|
||||
};
|
||||
};
|
||||
|
||||
// Check whether the deadline for each task is up or not, and responds appropriately
|
||||
function checkDeadlines() {
|
||||
// First of all check whether notifications are enabled or denied
|
||||
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||
notificationBtn.style.display = 'block';
|
||||
} else {
|
||||
notificationBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Grab the current time and date
|
||||
const now = new Date();
|
||||
|
||||
// From the now variable, store the current minutes, hours, day of the month, month, year and seconds
|
||||
const minuteCheck = now.getMinutes();
|
||||
const hourCheck = now.getHours();
|
||||
const dayCheck = now.getDate(); // Do not use getDay() that returns the day of the week, 1 to 7
|
||||
const monthCheck = now.getMonth();
|
||||
const yearCheck = now.getFullYear(); // Do not use getYear() that is deprecated.
|
||||
|
||||
// Open a new transaction
|
||||
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
|
||||
|
||||
// Open a cursor to iterate through all the data items in the IndexedDB
|
||||
objectStore.openCursor().onsuccess = (event) => {
|
||||
const cursor = event.target.result;
|
||||
if (!cursor) return;
|
||||
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
|
||||
|
||||
// convert the month names we have installed in the IDB into a month number that JavaScript will understand.
|
||||
// The JavaScript date object creates month values as a number between 0 and 11.
|
||||
const monthNumber = MONTHS.indexOf(month);
|
||||
if (monthNumber === -1) throw new Error('Incorrect month entered in database.');
|
||||
|
||||
// Check if the current hours, minutes, day, month and year values match the stored values for each task.
|
||||
// The parseInt() function transforms the value from a string to a number for comparison
|
||||
// (taking care of leading zeros, and removing spaces and underscores from the string).
|
||||
let matched = parseInt(hours) === hourCheck;
|
||||
matched &&= parseInt(minutes) === minuteCheck;
|
||||
matched &&= parseInt(day) === dayCheck;
|
||||
matched &&= parseInt(monthNumber) === monthCheck;
|
||||
matched &&= parseInt(year) === yearCheck;
|
||||
if (matched && notified === 'no') {
|
||||
// If the numbers all do match, run the createNotification() function to create a system notification
|
||||
// but only if the permission is set
|
||||
if (Notification.permission === 'granted') {
|
||||
createNotification(taskTitle);
|
||||
}
|
||||
}
|
||||
|
||||
// Move on to the next cursor item
|
||||
cursor.continue();
|
||||
};
|
||||
};
|
||||
|
||||
// Ask for permission when the 'Enable notifications' button is clicked
|
||||
function askNotificationPermission() {
|
||||
// Function to actually ask the permissions
|
||||
function handlePermission(permission) {
|
||||
// Whatever the user answers, we make sure Chrome stores the information
|
||||
if (!Reflect.has(Notification, 'permission')) {
|
||||
Notification.permission = permission;
|
||||
}
|
||||
|
||||
// Set the button to shown or hidden, depending on what the user answers
|
||||
if (Notification.permission === 'denied' || Notification.permission === 'default') {
|
||||
notificationBtn.style.display = 'block';
|
||||
} else {
|
||||
notificationBtn.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the browser supports notifications
|
||||
if (!Reflect.has(window, 'Notification')) {
|
||||
console.log('This browser does not support notifications.');
|
||||
} else {
|
||||
if (checkNotificationPromise()) {
|
||||
Notification.requestPermission().then(handlePermission);
|
||||
} else {
|
||||
Notification.requestPermission(handlePermission);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check whether browser supports the promise version of requestPermission()
|
||||
// Safari only supports the old callback-based version
|
||||
function checkNotificationPromise() {
|
||||
try {
|
||||
Notification.requestPermission().then();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Wire up notification permission functionality to 'Enable notifications' button
|
||||
notificationBtn.addEventListener('click', askNotificationPermission);
|
||||
|
||||
function createListItem(contents) {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.textContent = contents;
|
||||
return listItem;
|
||||
};
|
||||
|
||||
// Create a notification with the given title
|
||||
function createNotification(title) {
|
||||
// Create and show the notification
|
||||
const img = '/to-do-notifications/img/icon-128.png';
|
||||
const text = `HEY! Your task "${title}" is now overdue.`;
|
||||
const notification = new Notification('To do list', { body: text, icon: img });
|
||||
|
||||
// We need to update the value of notified to 'yes' in this particular data object, so the
|
||||
// notification won't be set off on it again
|
||||
|
||||
// First open up a transaction
|
||||
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
|
||||
|
||||
// Get the to-do list object that has this title as its title
|
||||
const objectStoreTitleRequest = objectStore.get(title);
|
||||
|
||||
objectStoreTitleRequest.onsuccess = () => {
|
||||
// Grab the data object returned as the result
|
||||
const data = objectStoreTitleRequest.result;
|
||||
|
||||
// Update the notified value in the object to 'yes'
|
||||
data.notified = 'yes';
|
||||
|
||||
// Create another request that inserts the item back into the database
|
||||
const updateTitleRequest = objectStore.put(data);
|
||||
|
||||
// When this new request succeeds, run the displayData() function again to update the display
|
||||
updateTitleRequest.onsuccess = () => {
|
||||
displayData();
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Using a setInterval to run the checkDeadlines() function every second
|
||||
setInterval(checkDeadlines, 1000);
|
||||
}
|
||||
|
||||
// Helper function returning the day of the month followed by an ordinal (st, nd, or rd)
|
||||
function ordinal(day) {
|
||||
const n = day.toString();
|
||||
const last = n.slice(-1);
|
||||
if (last === '1' && n !== '11') return `${n}st`;
|
||||
if (last === '2' && n !== '12') return `${n}nd`;
|
||||
if (last === '3' && n !== '13') return `${n}rd`;
|
||||
return `${n}th`;
|
||||
};
|
||||
@@ -1,248 +0,0 @@
|
||||
/* Basic set up + sizing for containers */
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 10px;
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
background: #111;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 50rem;
|
||||
position: relative;
|
||||
background: #d88;
|
||||
margin: 0 auto;
|
||||
border-left: 2px solid #d33;
|
||||
border-right: 2px solid #d33;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
text-align: center;
|
||||
background: #d88;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 6rem;
|
||||
margin: 0;
|
||||
background: #d66;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
/* Bottom toolbar styling */
|
||||
|
||||
#toolbar {
|
||||
position: relative;
|
||||
height: 6rem;
|
||||
width: 100%;
|
||||
background: #d66;
|
||||
border-top: 2px solid #d33;
|
||||
border-bottom: 2px solid #d33;
|
||||
}
|
||||
|
||||
#enable,
|
||||
input[type="submit"] {
|
||||
line-height: 1.8;
|
||||
font-size: 1.3rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
color: black;
|
||||
text-shadow: 1px 1px 1px black;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
inset 0px 5px 3px rgba(255, 255, 255, 0.2),
|
||||
inset 0px -5px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#enable {
|
||||
position: absolute;
|
||||
bottom: 0.3rem;
|
||||
right: 0.3rem;
|
||||
}
|
||||
|
||||
#notifications {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
padding: 0.3rem;
|
||||
background: #ddd;
|
||||
position: absolute;
|
||||
top: 0rem;
|
||||
left: 0rem;
|
||||
height: 5.4rem;
|
||||
width: 50%;
|
||||
overflow: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#notifications li {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
/* New item form styling */
|
||||
|
||||
.form-box {
|
||||
background: #d66;
|
||||
width: 85%;
|
||||
padding: 1rem;
|
||||
margin: 2rem auto;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
form div {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
form .full-width {
|
||||
margin: 1rem auto 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form .half-width {
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
form .third-width {
|
||||
width: 33%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
form div label {
|
||||
width: 10rem;
|
||||
float: left;
|
||||
padding-right: 1rem;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
form .full-width input {
|
||||
width: 30rem;
|
||||
}
|
||||
|
||||
form .half-width input {
|
||||
width: 8.75rem;
|
||||
}
|
||||
|
||||
form .third-width select {
|
||||
width: 13.5rem;
|
||||
}
|
||||
|
||||
form div input[type="submit"] {
|
||||
clear: both;
|
||||
width: 20rem;
|
||||
display: block;
|
||||
height: 3rem;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
/* || tasks box */
|
||||
|
||||
.task-box {
|
||||
width: 85%;
|
||||
padding: 1rem;
|
||||
margin: 2rem auto;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.task-box ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-box li {
|
||||
list-style-type: none;
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid #d33;
|
||||
}
|
||||
|
||||
.task-box li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.task-box li:last-child {
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.task-box button {
|
||||
margin-left: 2rem;
|
||||
font-size: 1.6rem;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 5px;
|
||||
box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.5) 1px 1px 1px black;
|
||||
}
|
||||
|
||||
/* setting cursor for interactive controls */
|
||||
|
||||
button,
|
||||
input[type="submit"],
|
||||
select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* media query for small screens */
|
||||
|
||||
@media (max-width: 32rem) {
|
||||
body {
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
form div {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
form .full-width {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
form .half-width {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
|
||||
form .third-width {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
|
||||
form div label {
|
||||
width: 36%;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
form input,
|
||||
form select,
|
||||
form label {
|
||||
line-height: 2.5rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
form .full-width input {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
form .half-width input {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
form .third-width select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#enable {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Playwright Parent Project</name>
|
||||
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
|
||||
@@ -45,7 +45,7 @@
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.parameters>true</maven.compiler.parameters>
|
||||
<gson.version>2.12.1</gson.version>
|
||||
<junit.version>5.12.1</junit.version>
|
||||
<junit.version>5.11.4</junit.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<websocket.version>1.6.0</websocket.version>
|
||||
<opentest4j.version>1.3.0</opentest4j.version>
|
||||
@@ -106,7 +106,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -116,17 +116,17 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -159,7 +159,6 @@
|
||||
</configurationParameters>
|
||||
</properties>
|
||||
<failIfNoTests>false</failIfNoTests>
|
||||
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
|
||||
<!-- Activate the use of TCP to transmit events to the plugin and avoid
|
||||
[WARNING] Corrupted STDOUT by directly writing to native stream in forked JVM -->
|
||||
<forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.51.1
|
||||
1.50.1-beta-1738592302000
|
||||
|
||||
+1
-11
@@ -24,14 +24,4 @@ else
|
||||
fi;
|
||||
|
||||
./generate_api.sh
|
||||
./update_readme.sh
|
||||
|
||||
node -e "$(cat <<EOF
|
||||
let [majorVersion, minorVersion] = process.argv[1].split('-')[0].split('.').map(part => parseInt(part, 10));
|
||||
minorVersion[1]--;
|
||||
const previousMajorVersion = majorVersion + '.' + minorVersion + '.0';
|
||||
fs.writeFileSync('../examples/pom.xml', fs.readFileSync('../examples/pom.xml', 'utf8')
|
||||
.replace(/<playwright\.version>.*<\/playwright\.version>/, '<playwright\.version>' + previousMajorVersion + '</playwright\.version>')
|
||||
);
|
||||
EOF
|
||||
)" $NEW_VERSION
|
||||
./update_readme.sh
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>api-generator</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Playwright - API Generator</name>
|
||||
<description>
|
||||
This is an internal module used to generate Java API from the upstream Playwright
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-cli-fatjar</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Test Playwright Command Line FatJar</name>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-cli-version</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Test Playwright Command Line Version</name>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-local-installation</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Test local installation</name>
|
||||
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
|
||||
<properties>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-spring-boot-starter</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Test Playwright With Spring Boot</name>
|
||||
<properties>
|
||||
<spring.version>2.4.3</spring.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>update-version</artifactId>
|
||||
<version>1.51.0</version>
|
||||
<version>1.50.0</version>
|
||||
<name>Playwright - Update Version in Documentation</name>
|
||||
<description>
|
||||
This is an internal module used to update versions in the documentation based on
|
||||
|
||||
Reference in New Issue
Block a user