1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2026 Paul Adamson
// Licensed under the Apache License, Version 2.0
//
// FileChooser - plain data struct constructed from "fileChooser" event params.
//
// FileChooser is NOT a ChannelOwner. It is constructed directly from
// the event params when a "fileChooser" event is received on the Page channel.
// The event params contain an ElementHandle GUID and an isMultiple flag.
//
// See: <https://playwright.dev/docs/api/class-filechooser>
use crate::error::Result;
use crate::protocol::ElementHandle;
use std::path::PathBuf;
use std::sync::Arc;
/// Represents a file chooser dialog triggered by an `<input type="file">` element.
///
/// `FileChooser` objects are dispatched by the `"fileChooser"` event on
/// [`Page`](crate::protocol::Page) via [`on_filechooser`](crate::protocol::Page::on_filechooser)
/// and [`expect_file_chooser`](crate::protocol::Page::expect_file_chooser).
///
/// # Usage
///
/// Use [`set_files`](FileChooser::set_files) to satisfy the file chooser by providing
/// file paths. The files are read from disk and sent to the browser.
///
/// # Example
///
/// ```no_run
/// use playwright_rs::protocol::Playwright;
/// use std::path::PathBuf;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let playwright = Playwright::launch().await?;
/// let browser = playwright.chromium().launch().await?;
/// let page = browser.new_page().await?;
///
/// page.set_content(
/// r#"<input type="file" id="f" />"#,
/// None
/// ).await?;
///
/// // Set up the waiter BEFORE the action that triggers the file chooser
/// let waiter = page.expect_file_chooser(None).await?;
///
/// // Click the file input to open the chooser
/// page.locator("#f").click(None).await?;
///
/// // Resolve the waiter and set files
/// let chooser = waiter.wait().await?;
/// chooser.set_files(&[PathBuf::from("/tmp/test.txt")]).await?;
///
/// browser.close().await?;
/// Ok(())
/// }
/// ```
///
/// See: <https://playwright.dev/docs/api/class-filechooser>
#[derive(Clone)]
pub struct FileChooser {
/// Back-reference to the page that owns this file chooser
page: crate::protocol::Page,
/// The `<input type="file">` element that triggered the chooser
element: Arc<ElementHandle>,
/// Whether the input accepts multiple files
is_multiple: bool,
}
impl FileChooser {
/// Creates a new `FileChooser` from event params.
///
/// Called by `Page::on_event("fileChooser")` when a fileChooser event
/// is received from the Playwright server.
pub(crate) fn new(
page: crate::protocol::Page,
element: Arc<ElementHandle>,
is_multiple: bool,
) -> Self {
Self {
page,
element,
is_multiple,
}
}
/// Returns the page that owns this file chooser.
///
/// See: <https://playwright.dev/docs/api/class-filechooser#file-chooser-page>
pub fn page(&self) -> &crate::protocol::Page {
&self.page
}
/// Returns the `<input type="file">` element that triggered this chooser.
///
/// See: <https://playwright.dev/docs/api/class-filechooser#file-chooser-element>
pub fn element(&self) -> Arc<ElementHandle> {
self.element.clone()
}
/// Returns `true` if the file input accepts multiple files.
///
/// This reflects the `multiple` attribute on the underlying `<input type="file">`.
///
/// See: <https://playwright.dev/docs/api/class-filechooser#file-chooser-is-multiple>
pub fn is_multiple(&self) -> bool {
self.is_multiple
}
/// Sets files on the associated `<input type="file">` element.
///
/// Reads each file from disk, encodes as base64, and sends a `setInputFiles`
/// RPC directly on the ElementHandle channel. This satisfies the file chooser
/// dialog without requiring any OS-level file picker interaction.
///
/// # Arguments
///
/// * `files` - Slice of file paths to set on the input element
///
/// # Errors
///
/// Returns error if:
/// - Any file cannot be read from disk
/// - The RPC call to the browser fails
///
/// # Example
///
/// ```no_run
/// # use playwright_rs::Playwright;
/// # use std::path::PathBuf;
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// # let pw = Playwright::launch().await?;
/// # let browser = pw.chromium().launch().await?;
/// # let page = browser.new_page().await?;
/// let waiter = page.expect_file_chooser(None).await?;
/// page.locator("input[type=file]").click(None).await?;
/// let chooser = waiter.wait().await?;
/// chooser.set_files(&[PathBuf::from("/tmp/upload.txt")]).await?;
/// # Ok(())
/// # }
/// ```
///
/// See: <https://playwright.dev/docs/api/class-filechooser#file-chooser-set-files>
#[tracing::instrument(level = "debug", skip_all, fields(count = files.len()))]
pub async fn set_files(&self, files: &[PathBuf]) -> Result<()> {
self.element.set_input_files(files).await
}
}
impl std::fmt::Debug for FileChooser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileChooser")
.field("is_multiple", &self.is_multiple)
.finish()
}
}