diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCanceled.java b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCanceled.java new file mode 100644 index 0000000000000..f98059cf57b00 --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCanceled.java @@ -0,0 +1,84 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.bidi.browsingcontext; + +import static java.util.Objects.requireNonNullElse; + +import org.openqa.selenium.json.JsonInput; + +public class DownloadCanceled extends NavigationInfo { + + private final String status; + + private static final String CANCELED = "canceled"; + + DownloadCanceled( + String browsingContextId, String navigationId, long timestamp, String url, String status) { + super(browsingContextId, navigationId, timestamp, url); + this.status = requireNonNullElse(status, CANCELED); + } + + public static DownloadCanceled fromJson(JsonInput input) { + String browsingContextId = null; + String navigationId = null; + long timestamp = 0; + String url = null; + String status = CANCELED; + + input.beginObject(); + while (input.hasNext()) { + switch (input.nextName()) { + case "context": + browsingContextId = input.read(String.class); + break; + + case "navigation": + navigationId = input.read(String.class); + break; + + case "timestamp": + timestamp = input.read(Long.class); + break; + + case "url": + url = input.read(String.class); + break; + + case "status": + status = input.read(String.class); + if (!CANCELED.equals(status)) { + throw new IllegalArgumentException( + "Expected status '" + CANCELED + "' , but got: " + status); + } + break; + + default: + input.skipValue(); + break; + } + } + + input.endObject(); + + return new DownloadCanceled(browsingContextId, navigationId, timestamp, url, status); + } + + public String getStatus() { + return status; + } +} diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCompleted.java b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCompleted.java new file mode 100644 index 0000000000000..270a33871e3ce --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadCompleted.java @@ -0,0 +1,100 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.bidi.browsingcontext; + +import static java.util.Objects.requireNonNullElse; + +import org.openqa.selenium.json.JsonInput; + +public class DownloadCompleted extends NavigationInfo { + + private final String status; + private final String filepath; + + private static final String COMPLETE = "complete"; + + DownloadCompleted( + String browsingContextId, + String navigationId, + long timestamp, + String url, + String status, + String filepath) { + super(browsingContextId, navigationId, timestamp, url); + this.status = requireNonNullElse(status, COMPLETE); + this.filepath = filepath; + } + + public static DownloadCompleted fromJson(JsonInput input) { + String browsingContextId = null; + String navigationId = null; + long timestamp = 0; + String url = null; + String status = COMPLETE; + String filepath = null; + + input.beginObject(); + while (input.hasNext()) { + switch (input.nextName()) { + case "context": + browsingContextId = input.read(String.class); + break; + + case "navigation": + navigationId = input.read(String.class); + break; + + case "timestamp": + timestamp = input.read(Long.class); + break; + + case "url": + url = input.read(String.class); + break; + + case "status": + status = input.read(String.class); + if (!COMPLETE.equals(status)) { + throw new IllegalArgumentException( + "Expected status '" + COMPLETE + "' , but got: " + status); + } + break; + + case "filepath": + filepath = input.read(String.class); + break; + + default: + input.skipValue(); + break; + } + } + + input.endObject(); + + return new DownloadCompleted(browsingContextId, navigationId, timestamp, url, status, filepath); + } + + public String getStatus() { + return status; + } + + public String getFilepath() { + return filepath; + } +} diff --git a/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadEnded.java b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadEnded.java new file mode 100644 index 0000000000000..8d097db435d64 --- /dev/null +++ b/java/src/org/openqa/selenium/bidi/browsingcontext/DownloadEnded.java @@ -0,0 +1,64 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.bidi.browsingcontext; + +import java.io.StringReader; +import java.util.Map; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonInput; + +public class DownloadEnded { + + private static final String CANCELED = "canceled"; + private static final String COMPLETE = "complete"; + + private final NavigationInfo downloadParams; + + public DownloadEnded(NavigationInfo downloadParams) { + this.downloadParams = downloadParams; + } + + public static DownloadEnded fromJson(JsonInput input) { + Map jsonMap = input.read(Map.class); + String status = (String) jsonMap.get("status"); + + try (StringReader reader = new StringReader(new Json().toJson(jsonMap)); + JsonInput jsonInput = new Json().newInput(reader)) { + if (CANCELED.equals(status)) { + return new DownloadEnded(DownloadCanceled.fromJson(jsonInput)); + } else if (COMPLETE.equals(status)) { + return new DownloadEnded(DownloadCompleted.fromJson(jsonInput)); + } else { + throw new IllegalArgumentException( + "status must be either '" + CANCELED + "' or '" + COMPLETE + "', but got: " + status); + } + } + } + + public NavigationInfo getDownloadParams() { + return downloadParams; + } + + public boolean isCanceled() { + return downloadParams instanceof DownloadCanceled; + } + + public boolean isCompleted() { + return downloadParams instanceof DownloadCompleted; + } +} diff --git a/java/src/org/openqa/selenium/bidi/module/BrowsingContextInspector.java b/java/src/org/openqa/selenium/bidi/module/BrowsingContextInspector.java index 4b7637e2af22f..ac0b9fa0b4904 100644 --- a/java/src/org/openqa/selenium/bidi/module/BrowsingContextInspector.java +++ b/java/src/org/openqa/selenium/bidi/module/BrowsingContextInspector.java @@ -28,12 +28,7 @@ import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.bidi.Event; import org.openqa.selenium.bidi.HasBiDi; -import org.openqa.selenium.bidi.browsingcontext.BrowsingContextInfo; -import org.openqa.selenium.bidi.browsingcontext.DownloadInfo; -import org.openqa.selenium.bidi.browsingcontext.HistoryUpdated; -import org.openqa.selenium.bidi.browsingcontext.NavigationInfo; -import org.openqa.selenium.bidi.browsingcontext.UserPromptClosed; -import org.openqa.selenium.bidi.browsingcontext.UserPromptOpened; +import org.openqa.selenium.bidi.browsingcontext.*; import org.openqa.selenium.internal.Require; import org.openqa.selenium.json.Json; import org.openqa.selenium.json.JsonInput; @@ -70,6 +65,14 @@ public class BrowsingContextInspector implements AutoCloseable { } }; + private final Function, DownloadEnded> downloadEndMapper = + params -> { + try (StringReader reader = new StringReader(JSON.toJson(params)); + JsonInput input = JSON.newInput(reader)) { + return input.read(DownloadEnded.class); + } + }; + private final Event browsingContextCreated = new Event<>("browsingContext.contextCreated", browsingContextInfoMapper); @@ -91,6 +94,9 @@ public class BrowsingContextInspector implements AutoCloseable { private final Event downloadWillBeginEvent = new Event<>("browsingContext.downloadWillBegin", downloadWillBeginMapper); + private final Event downloadEndEvent = + new Event<>("browsingContext.downloadEnd", downloadEndMapper); + private final Event userPromptOpened = new Event<>( "browsingContext.userPromptOpened", @@ -171,6 +177,14 @@ public void onDownloadWillBegin(Consumer consumer) { } } + public void onDownloadEnd(Consumer consumer) { + if (browsingContextIds.isEmpty()) { + this.bidi.addListener(downloadEndEvent, consumer); + } else { + this.bidi.addListener(browsingContextIds, downloadEndEvent, consumer); + } + } + public void onNavigationAborted(Consumer consumer) { addNavigationEventListener("browsingContext.navigationAborted", consumer); } @@ -227,6 +241,7 @@ public void close() { this.bidi.clearListener(userPromptClosed); this.bidi.clearListener(historyUpdated); this.bidi.clearListener(downloadWillBeginEvent); + this.bidi.clearListener(downloadEndEvent); navigationEventSet.forEach(this.bidi::clearListener); } diff --git a/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInspectorTest.java b/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInspectorTest.java index 93ddcfcb83d10..e5882c0375a3b 100644 --- a/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInspectorTest.java +++ b/java/test/org/openqa/selenium/bidi/browsingcontext/BrowsingContextInspectorTest.java @@ -255,6 +255,28 @@ void canListenToDownloadWillBeginEvent() } } + @Test + @NeedsFreshDriver + @NotYetImplemented(FIREFOX) + void canListenToDownloadEnd() throws ExecutionException, InterruptedException, TimeoutException { + try (BrowsingContextInspector inspector = new BrowsingContextInspector(driver)) { + CompletableFuture future = new CompletableFuture<>(); + + inspector.onDownloadEnd(future::complete); + + BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle()); + context.navigate(appServer.whereIs("/downloads/download.html"), ReadinessState.COMPLETE); + + driver.findElement(By.id("file-1")).click(); + + DownloadEnded downloadEnded = future.get(5, TimeUnit.SECONDS); + assertThat(downloadEnded.getDownloadParams().getBrowsingContextId()) + .isEqualTo(context.getId()); + assertThat(downloadEnded.isCompleted()).isTrue(); + assertThat(downloadEnded.getDownloadParams().getUrl()).contains("/downloads/file_1.txt"); + } + } + @Test @NeedsFreshDriver @NotYetImplemented(FIREFOX)