Skip to content

Commit ea49009

Browse files
asolntsevdiemol
andauthored
Show file modification time (#16589)
* split method LocalNode.downloadFile() to 3 smaller methods * GET /se/files -> list all downloaded files * POST /se/files -> get specific file by name * DELETE /se/files -> delete downloaded files * replace `ImmutableMap.of` by `Map.of` It's available from Java 9, and it also returns an immutable map. * add method to get downloaded files with meta-info from Grid To track the downloading progress and wait for the full download completion, we need to know this info about downloaded files: 1. File modification time 2. File size --------- Co-authored-by: Diego Molina <diemol@users.noreply.github.com>
1 parent 7610fad commit ea49009

File tree

4 files changed

+181
-67
lines changed

4 files changed

+181
-67
lines changed

java/src/org/openqa/selenium/HasDownloads.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,18 @@ static boolean isDownloadsEnabled(Capabilities capabilities) {
5454
* Gets the downloadable files.
5555
*
5656
* @return a list of downloadable files for each key
57+
* @deprecated Use method {@link #getDownloadedFiles()} instead
5758
*/
59+
@Deprecated
5860
List<String> getDownloadableFiles();
5961

62+
/**
63+
* Gets all files downloaded by browser.
64+
*
65+
* @return a list of files with their name, size and time.
66+
*/
67+
List<DownloadedFile> getDownloadedFiles();
68+
6069
/**
6170
* Downloads a file to a given location.
6271
*
@@ -68,4 +77,34 @@ static boolean isDownloadsEnabled(Capabilities capabilities) {
6877

6978
/** Deletes the downloadable files. */
7079
void deleteDownloadableFiles();
80+
81+
class DownloadedFile {
82+
private final String name;
83+
private final long creationTime;
84+
private final long lastModifiedTime;
85+
private final long size;
86+
87+
public DownloadedFile(String name, long creationTime, long lastModifiedTime, long size) {
88+
this.name = name;
89+
this.creationTime = creationTime;
90+
this.lastModifiedTime = lastModifiedTime;
91+
this.size = size;
92+
}
93+
94+
public String getName() {
95+
return name;
96+
}
97+
98+
public long getCreationTime() {
99+
return creationTime;
100+
}
101+
102+
public long getLastModifiedTime() {
103+
return lastModifiedTime;
104+
}
105+
106+
public long getSize() {
107+
return size;
108+
}
109+
}
71110
}

java/src/org/openqa/selenium/grid/node/local/LocalNode.java

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package org.openqa.selenium.grid.node.local;
1919

2020
import static com.google.common.collect.ImmutableSet.toImmutableSet;
21+
import static java.nio.file.Files.readAttributes;
22+
import static org.openqa.selenium.HasDownloads.DownloadedFile;
2123
import static org.openqa.selenium.concurrent.ExecutorServices.shutdownGracefully;
2224
import static org.openqa.selenium.grid.data.Availability.DOWN;
2325
import static org.openqa.selenium.grid.data.Availability.DRAINING;
@@ -37,14 +39,14 @@
3739
import com.github.benmanes.caffeine.cache.Ticker;
3840
import com.google.common.annotations.VisibleForTesting;
3941
import com.google.common.collect.ImmutableList;
40-
import com.google.common.collect.ImmutableMap;
4142
import java.io.Closeable;
4243
import java.io.File;
4344
import java.io.IOException;
4445
import java.io.Serializable;
4546
import java.io.UncheckedIOException;
4647
import java.net.URI;
4748
import java.net.URISyntaxException;
49+
import java.nio.file.attribute.BasicFileAttributes;
4850
import java.time.Clock;
4951
import java.time.Duration;
5052
import java.time.Instant;
@@ -580,8 +582,8 @@ private boolean managedDownloadsRequested(Capabilities capabilities) {
580582
private Capabilities setDownloadsDirectory(TemporaryFilesystem downloadsTfs, Capabilities caps) {
581583
File tempDir = downloadsTfs.createTempDir("download", "");
582584
if (Browser.CHROME.is(caps) || Browser.EDGE.is(caps)) {
583-
ImmutableMap<String, Serializable> map =
584-
ImmutableMap.of(
585+
Map<String, Serializable> map =
586+
Map.of(
585587
"download.prompt_for_download",
586588
false,
587589
"download.default_directory",
@@ -592,8 +594,8 @@ private Capabilities setDownloadsDirectory(TemporaryFilesystem downloadsTfs, Cap
592594
return appendPrefs(caps, optionsKey, map);
593595
}
594596
if (Browser.FIREFOX.is(caps)) {
595-
ImmutableMap<String, Serializable> map =
596-
ImmutableMap.of(
597+
Map<String, Serializable> map =
598+
Map.of(
597599
"browser.download.folderList", 2, "browser.download.dir", tempDir.getAbsolutePath());
598600
return appendPrefs(caps, "moz:firefoxOptions", map);
599601
}
@@ -738,25 +740,50 @@ public HttpResponse downloadFile(HttpRequest req, SessionId id) {
738740
}
739741
File downloadsDirectory =
740742
Optional.ofNullable(tempFS.getBaseDir().listFiles()).orElse(new File[] {})[0];
741-
if (req.getMethod().equals(HttpMethod.GET)) {
742-
// User wants to list files that can be downloaded
743-
List<String> collected =
744-
Arrays.stream(Optional.ofNullable(downloadsDirectory.listFiles()).orElse(new File[] {}))
745-
.map(File::getName)
746-
.collect(Collectors.toList());
747-
ImmutableMap<String, Object> data = ImmutableMap.of("names", collected);
748-
ImmutableMap<String, Map<String, Object>> result = ImmutableMap.of("value", data);
749-
return new HttpResponse().setContent(asJson(result));
750-
}
751-
if (req.getMethod().equals(HttpMethod.DELETE)) {
752-
File[] files = Optional.ofNullable(downloadsDirectory.listFiles()).orElse(new File[] {});
753-
for (File file : files) {
754-
FileHandler.delete(file);
743+
744+
try {
745+
if (req.getMethod().equals(HttpMethod.GET)) {
746+
return listDownloadedFiles(downloadsDirectory);
755747
}
756-
Map<String, Object> toReturn = new HashMap<>();
757-
toReturn.put("value", null);
758-
return new HttpResponse().setContent(asJson(toReturn));
748+
if (req.getMethod().equals(HttpMethod.DELETE)) {
749+
return deleteDownloadedFile(downloadsDirectory);
750+
}
751+
return getDownloadedFile(req, downloadsDirectory);
752+
} catch (IOException e) {
753+
throw new UncheckedIOException(e);
759754
}
755+
}
756+
757+
/** User wants to list files that can be downloaded */
758+
private HttpResponse listDownloadedFiles(File downloadsDirectory) {
759+
File[] files = Optional.ofNullable(downloadsDirectory.listFiles()).orElse(new File[] {});
760+
List<String> fileNames = Arrays.stream(files).map(File::getName).collect(Collectors.toList());
761+
List<DownloadedFile> fileInfos =
762+
Arrays.stream(files).map(this::getFileInfo).collect(Collectors.toList());
763+
764+
Map<String, Object> data =
765+
Map.of(
766+
"names", fileNames,
767+
"files", fileInfos);
768+
Map<String, Map<String, Object>> result = Map.of("value", data);
769+
return new HttpResponse().setContent(asJson(result));
770+
}
771+
772+
private DownloadedFile getFileInfo(File file) {
773+
try {
774+
BasicFileAttributes attributes = readAttributes(file.toPath(), BasicFileAttributes.class);
775+
return new DownloadedFile(
776+
file.getName(),
777+
attributes.creationTime().toMillis(),
778+
attributes.lastModifiedTime().toMillis(),
779+
attributes.size());
780+
} catch (IOException e) {
781+
throw new UncheckedIOException("Failed to get file attributes: " + file.getAbsolutePath(), e);
782+
}
783+
}
784+
785+
private HttpResponse getDownloadedFile(HttpRequest req, File downloadsDirectory)
786+
throws IOException {
760787
String raw = string(req);
761788
if (raw.isEmpty()) {
762789
throw new WebDriverException(
@@ -771,30 +798,37 @@ public HttpResponse downloadFile(HttpRequest req, SessionId id) {
771798
new WebDriverException(
772799
"Please specify file to download in payload as {\"name\":"
773800
+ " \"fileToDownload\"}"));
774-
try {
775-
File[] allFiles =
776-
Optional.ofNullable(downloadsDirectory.listFiles((dir, name) -> name.equals(filename)))
777-
.orElse(new File[] {});
778-
if (allFiles.length == 0) {
779-
throw new WebDriverException(
780-
String.format(
781-
"Cannot find file [%s] in directory %s.",
782-
filename, downloadsDirectory.getAbsolutePath()));
783-
}
784-
if (allFiles.length != 1) {
785-
throw new WebDriverException(
786-
String.format("Expected there to be only 1 file. There were: %s.", allFiles.length));
787-
}
788-
String content = Zip.zip(allFiles[0]);
789-
ImmutableMap<String, Object> data =
790-
ImmutableMap.of(
791-
"filename", filename,
792-
"contents", content);
793-
ImmutableMap<String, Map<String, Object>> result = ImmutableMap.of("value", data);
794-
return new HttpResponse().setContent(asJson(result));
795-
} catch (IOException e) {
796-
throw new UncheckedIOException(e);
801+
File[] allFiles =
802+
Optional.ofNullable(downloadsDirectory.listFiles((dir, name) -> name.equals(filename)))
803+
.orElse(new File[] {});
804+
if (allFiles.length == 0) {
805+
throw new WebDriverException(
806+
String.format(
807+
"Cannot find file [%s] in directory %s.",
808+
filename, downloadsDirectory.getAbsolutePath()));
809+
}
810+
if (allFiles.length != 1) {
811+
throw new WebDriverException(
812+
String.format("Expected there to be only 1 file. There were: %s.", allFiles.length));
813+
}
814+
String content = Zip.zip(allFiles[0]);
815+
Map<String, Object> data =
816+
Map.of(
817+
"filename", filename,
818+
"file", getFileInfo(allFiles[0]),
819+
"contents", content);
820+
Map<String, Map<String, Object>> result = Map.of("value", data);
821+
return new HttpResponse().setContent(asJson(result));
822+
}
823+
824+
private HttpResponse deleteDownloadedFile(File downloadsDirectory) {
825+
File[] files = Optional.ofNullable(downloadsDirectory.listFiles()).orElse(new File[] {});
826+
for (File file : files) {
827+
FileHandler.delete(file);
797828
}
829+
Map<String, Object> toReturn = new HashMap<>();
830+
toReturn.put("value", null);
831+
return new HttpResponse().setContent(asJson(toReturn));
798832
}
799833

800834
@Override
@@ -829,7 +863,7 @@ public HttpResponse uploadFile(HttpRequest req, SessionId id) {
829863
String.format("Expected there to be only 1 file. There were: %s", allFiles.length));
830864
}
831865

832-
ImmutableMap<String, Object> result = ImmutableMap.of("value", allFiles[0].getAbsolutePath());
866+
Map<String, Object> result = Map.of("value", allFiles[0].getAbsolutePath());
833867

834868
return new HttpResponse().setContent(asJson(result));
835869
}
@@ -1063,13 +1097,17 @@ private boolean decrementSessionCount() {
10631097
}
10641098

10651099
private Map<String, Object> toJson() {
1066-
return ImmutableMap.of(
1067-
"id", getId(),
1068-
"uri", externalUri,
1069-
"maxSessions", maxSessionCount,
1070-
"draining", isDraining(),
1100+
return Map.of(
1101+
"id",
1102+
getId(),
1103+
"uri",
1104+
externalUri,
1105+
"maxSessions",
1106+
maxSessionCount,
1107+
"draining",
1108+
isDraining(),
10711109
"capabilities",
1072-
factories.stream().map(SessionSlot::getStereotype).collect(Collectors.toSet()));
1110+
factories.stream().map(SessionSlot::getStereotype).collect(Collectors.toSet()));
10731111
}
10741112

10751113
public static class Builder {

java/src/org/openqa/selenium/remote/RemoteWebDriver.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static java.util.Collections.singleton;
2121
import static java.util.concurrent.TimeUnit.SECONDS;
2222
import static java.util.logging.Level.SEVERE;
23+
import static org.openqa.selenium.HasDownloads.DownloadedFile;
2324
import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME;
2425

2526
import java.io.IOException;
@@ -672,19 +673,46 @@ public boolean isDownloadsEnabled() {
672673
}
673674

674675
/**
675-
* Retrieves the names of the downloadable files.
676+
* Retrieves the names of the files downloaded by browser.
676677
*
677-
* @return A list containing the names of the downloadable files.
678+
* @return A list containing the names of the downloaded files.
678679
* @throws WebDriverException if capability to enable downloads is not set
680+
* @deprecated Use method {@link #getDownloadedFiles()} instead
679681
*/
680682
@Override
681683
@SuppressWarnings("unchecked")
684+
@Deprecated
682685
public List<String> getDownloadableFiles() {
683686
requireDownloadsEnabled(capabilities);
684687

685688
Response response = execute(DriverCommand.GET_DOWNLOADABLE_FILES);
686-
Map<String, List<String>> value = (Map<String, List<String>>) response.getValue();
687-
return value.get("names");
689+
Map<String, Object> value = (Map<String, Object>) response.getValue();
690+
return (List<String>) value.get("names");
691+
}
692+
693+
/**
694+
* Retrieves the list of files downloaded by browser.
695+
*
696+
* @return A list containing the names, size etc. of the downloaded files.
697+
* @throws WebDriverException if capability to enable downloads is not set
698+
*/
699+
@Override
700+
@SuppressWarnings("unchecked")
701+
public List<DownloadedFile> getDownloadedFiles() {
702+
requireDownloadsEnabled(capabilities);
703+
704+
Response response = execute(DriverCommand.GET_DOWNLOADABLE_FILES);
705+
Map<String, Object> value = (Map<String, Object>) response.getValue();
706+
List<Map<String, Object>> files = (List<Map<String, Object>>) value.get("files");
707+
return files.stream()
708+
.map(
709+
file ->
710+
new DownloadedFile(
711+
(String) file.get("name"),
712+
(Long) file.get("creationTime"),
713+
(Long) file.get("lastModifiedTime"),
714+
(Long) file.get("size")))
715+
.collect(Collectors.toUnmodifiableList());
688716
}
689717

690718
/**

java/test/org/openqa/selenium/grid/router/RemoteWebDriverDownloadTest.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
package org.openqa.selenium.grid.router;
1919

2020
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.openqa.selenium.HasDownloads.DownloadedFile;
2122
import static org.openqa.selenium.remote.CapabilityType.ENABLE_DOWNLOADS;
2223
import static org.openqa.selenium.testing.drivers.Browser.IE;
2324
import static org.openqa.selenium.testing.drivers.Browser.SAFARI;
2425

26+
import java.io.File;
2527
import java.io.IOException;
2628
import java.io.StringReader;
2729
import java.net.URL;
@@ -34,6 +36,7 @@
3436
import java.util.Set;
3537
import java.util.concurrent.ExecutorService;
3638
import java.util.concurrent.Executors;
39+
import java.util.stream.Collectors;
3740
import org.junit.jupiter.api.AfterEach;
3841
import org.junit.jupiter.api.Assertions;
3942
import org.junit.jupiter.api.BeforeEach;
@@ -114,20 +117,24 @@ void canListDownloadedFiles() {
114117
driver.findElement(By.id("file-1")).click();
115118
driver.findElement(By.id("file-2")).click();
116119

120+
HasDownloads hasDownloads = (HasDownloads) driver;
117121
new WebDriverWait(driver, Duration.ofSeconds(5))
118122
.until(
119123
d ->
120-
((HasDownloads) d)
121-
.getDownloadableFiles().stream()
122-
// ensure we hit no temporary file created by the browser while
123-
// downloading
124-
.filter((f) -> FILE_EXTENSIONS.stream().anyMatch(f::endsWith))
125-
.count()
124+
hasDownloads.getDownloadableFiles().stream()
125+
// ensure we hit no temporary file created by the browser while
126+
// downloading
127+
.filter((f) -> FILE_EXTENSIONS.stream().anyMatch(f::endsWith))
128+
.count()
126129
== 2);
127130

128-
List<String> downloadableFiles = ((HasDownloads) driver).getDownloadableFiles();
131+
List<String> downloadableFiles = hasDownloads.getDownloadableFiles();
129132
assertThat(downloadableFiles).contains("file_1.txt", "file_2.jpg");
130133

134+
List<DownloadedFile> downloadedFiles = hasDownloads.getDownloadedFiles();
135+
assertThat(downloadedFiles.stream().map(f -> f.getName()).collect(Collectors.toList()))
136+
.contains("file_1.txt", "file_2.jpg");
137+
131138
driver.quit();
132139
}
133140

@@ -150,13 +157,15 @@ void canDownloadFiles() throws IOException {
150157
// ensure we hit no temporary file created by the browser while downloading
151158
.anyMatch((f) -> FILE_EXTENSIONS.stream().anyMatch(f::endsWith)));
152159

153-
String fileName = ((HasDownloads) driver).getDownloadableFiles().get(0);
160+
DownloadedFile file = ((HasDownloads) driver).getDownloadedFiles().get(0);
154161

155162
Path targetLocation = Files.createTempDirectory("download");
156-
((HasDownloads) driver).downloadFile(fileName, targetLocation);
163+
((HasDownloads) driver).downloadFile(file.getName(), targetLocation);
157164

158-
String fileContent = String.join("", Files.readAllLines(targetLocation.resolve(fileName)));
159-
assertThat(fileContent).isEqualTo("Hello, World!");
165+
File localFile = targetLocation.resolve(file.getName()).toFile();
166+
assertThat(localFile).hasName(file.getName());
167+
assertThat(localFile).hasSize(file.getSize());
168+
assertThat(localFile).content().isEqualToIgnoringNewLines("Hello, World!");
160169

161170
driver.quit();
162171
}

0 commit comments

Comments
 (0)