playwright_core/protocol/
download.rs

1// Copyright 2024 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// Download protocol object
5//
6// Represents a file download triggered by the page.
7// Downloads are dispatched via page.on('download') events.
8
9use crate::channel_owner::ChannelOwner;
10use crate::error::Result;
11use serde_json::json;
12use std::path::PathBuf;
13use std::sync::Arc;
14
15/// Download represents a file download triggered by the page.
16///
17/// Downloads are dispatched via the page.on('download') event.
18/// The download will be automatically deleted when the browser context closes.
19///
20/// NOTE: Unlike other protocol objects, Download is a wrapper around the Artifact
21/// protocol object. The URL and suggested_filename come from the download event params,
22/// while the actual file operations are delegated to the underlying Artifact.
23///
24/// See: <https://playwright.dev/docs/api/class-download>
25#[derive(Clone)]
26pub struct Download {
27    /// Reference to the underlying Artifact protocol object
28    artifact: Arc<dyn ChannelOwner>,
29    /// URL from download event params
30    url: String,
31    /// Suggested filename from download event params
32    suggested_filename: String,
33}
34
35impl Download {
36    /// Creates a new Download from an Artifact and event params
37    ///
38    /// This is NOT created via the object factory, but rather constructed
39    /// directly from the download event params which contain {url, suggestedFilename, artifact}.
40    ///
41    /// # Arguments
42    ///
43    /// * `artifact` - The Artifact protocol object (from event params)
44    /// * `url` - Download URL (from event params)
45    /// * `suggested_filename` - Suggested filename (from event params)
46    pub fn from_artifact(
47        artifact: Arc<dyn ChannelOwner>,
48        url: String,
49        suggested_filename: String,
50    ) -> Self {
51        Self {
52            artifact,
53            url,
54            suggested_filename,
55        }
56    }
57
58    /// Returns the download URL.
59    ///
60    /// See: <https://playwright.dev/docs/api/class-download#download-url>
61    pub fn url(&self) -> &str {
62        &self.url
63    }
64
65    /// Returns the suggested filename for the download.
66    ///
67    /// This is typically the server-suggested filename from the Content-Disposition
68    /// header or the HTML download attribute.
69    ///
70    /// See: <https://playwright.dev/docs/api/class-download#download-suggested-filename>
71    pub fn suggested_filename(&self) -> &str {
72        &self.suggested_filename
73    }
74
75    /// Returns the underlying Artifact's channel for protocol communication
76    fn channel(&self) -> &crate::channel::Channel {
77        self.artifact.channel()
78    }
79
80    /// Returns the path to the downloaded file after it completes.
81    ///
82    /// This method waits for the download to finish if necessary.
83    /// Returns an error if the download fails or is canceled.
84    ///
85    /// See: <https://playwright.dev/docs/api/class-download#download-path>
86    pub async fn path(&self) -> Result<Option<PathBuf>> {
87        #[derive(serde::Deserialize)]
88        struct PathResponse {
89            value: Option<String>,
90        }
91
92        let result: PathResponse = self.channel().send("path", json!({})).await?;
93
94        Ok(result.value.map(PathBuf::from))
95    }
96
97    /// Saves the download to the specified path.
98    ///
99    /// This method can be safely called while the download is still in progress.
100    /// The file will be copied to the specified location after the download completes.
101    ///
102    /// # Example
103    ///
104    /// ```ignore
105    /// # use playwright_core::protocol::Download;
106    /// # async fn example(download: Download) -> Result<(), Box<dyn std::error::Error>> {
107    /// download.save_as("/path/to/save/file.pdf").await?;
108    /// # Ok(())
109    /// # }
110    /// ```
111    ///
112    /// See: <https://playwright.dev/docs/api/class-download#download-save-as>
113    pub async fn save_as(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
114        let path_str = path
115            .as_ref()
116            .to_str()
117            .ok_or_else(|| crate::error::Error::InvalidArgument("Invalid path".to_string()))?;
118
119        self.channel()
120            .send_no_result("saveAs", json!({ "path": path_str }))
121            .await?;
122
123        Ok(())
124    }
125
126    /// Cancels the download.
127    ///
128    /// After calling this method, `failure()` will return an error message.
129    ///
130    /// See: <https://playwright.dev/docs/api/class-download#download-cancel>
131    pub async fn cancel(&self) -> Result<()> {
132        self.channel().send_no_result("cancel", json!({})).await?;
133
134        Ok(())
135    }
136
137    /// Deletes the downloaded file.
138    ///
139    /// The download must be finished before calling this method.
140    ///
141    /// See: <https://playwright.dev/docs/api/class-download#download-delete>
142    pub async fn delete(&self) -> Result<()> {
143        self.channel().send_no_result("delete", json!({})).await?;
144
145        Ok(())
146    }
147
148    /// Returns the download error message if it failed, otherwise None.
149    ///
150    /// See: <https://playwright.dev/docs/api/class-download#download-failure>
151    pub async fn failure(&self) -> Result<Option<String>> {
152        #[derive(serde::Deserialize)]
153        struct FailureResponse {
154            error: Option<String>,
155        }
156
157        let result: FailureResponse = self.channel().send("failure", json!({})).await?;
158
159        Ok(result.error)
160    }
161}
162
163impl std::fmt::Debug for Download {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        f.debug_struct("Download")
166            .field("url", &self.url())
167            .field("suggested_filename", &self.suggested_filename())
168            .finish()
169    }
170}