Skip to main content

chrome_for_testing/api/
last_known_good_versions.rs

1use crate::api::channel::Channel;
2use crate::api::version::Version;
3use crate::api::{API_BASE_URL, Download};
4use crate::error::Result;
5use serde::Deserialize;
6use std::collections::HashMap;
7
8/// JSON Example:
9/// ```json
10/// {
11///     "timestamp": "2025-01-05T22:09:08.729Z",
12///     "channels": {
13///         "Stable": {
14///             "channel": "Stable",
15///             "version": "131.0.6778.204",
16///             "revision": "1368529",
17///             "downloads": {
18///                 "chrome": [
19///                     {
20///                         "platform": "linux64",
21///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chrome-linux64.zip"
22///                     },
23///                     {
24///                         "platform": "mac-arm64",
25///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/mac-arm64/chrome-mac-arm64.zip"
26///                     },
27///                     {
28///                         "platform": "mac-x64",
29///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/mac-x64/chrome-mac-x64.zip"
30///                     },
31///                     {
32///                         "platform": "win32",
33///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/win32/chrome-win32.zip"
34///                     },
35///                     {
36///                         "platform": "win64",
37///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/win64/chrome-win64.zip"
38///                     }
39///                 ],
40///                 "chromedriver": [
41///                     {
42///                         "platform": "linux64",
43///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chromedriver-linux64.zip"
44///                     },
45///                     ...
46///                 ],
47///                 "chrome-headless-shell": [
48///                     {
49///                         "platform": "linux64",
50///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chrome-headless-shell-linux64.zip"
51///                     },
52///                     ...
53///                 ]
54///             }
55///         },
56///         "Beta": {
57///             "channel": "Beta",
58///             "version": "132.0.6834.57",
59///             "revision": "1381561",
60///             "downloads": {
61///                 "chrome": [
62///                    ...
63///                 ],
64///                 "chromedriver": [
65///                     ...
66///                 ],
67///                 "chrome-headless-shell": [
68///                     ...
69///                 ]
70///             }
71///         },
72///         "Dev": { ... },
73///         "Canary": { ... }
74///     }
75/// }
76/// ```
77const LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH: &str =
78    "/chrome-for-testing/last-known-good-versions-with-downloads.json";
79
80/// Download links for Chrome, `ChromeDriver`, and Chrome Headless Shell binaries for various
81/// platforms.
82#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
83pub struct Downloads {
84    /// Download links for Chrome binaries for various platforms.
85    pub chrome: Vec<Download>,
86
87    /// Download links for `ChromeDriver` binaries for various platforms.
88    pub chromedriver: Vec<Download>,
89
90    /// The "chrome-headless-shell" binary provides the "old" headless mode of Chrome, as described
91    /// in [this blog post](https://developer.chrome.com/blog/chrome-headless-shell).
92    /// For standard automated web-ui testing, you should pretty much always use the regular
93    /// `chrome` binary instead.
94    #[serde(rename = "chrome-headless-shell")]
95    pub chrome_headless_shell: Vec<Download>,
96}
97
98/// A Chrome version entry with channel information.
99#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
100pub struct VersionInChannel {
101    /// The release channel this version belongs to.
102    pub channel: Channel,
103
104    /// The version identifier.
105    pub version: Version,
106
107    /// The Chromium revision number.
108    pub revision: String,
109
110    /// Available downloads for this version.
111    pub downloads: Downloads,
112}
113
114/// Response structure for the "last known good versions" API endpoint.
115///
116/// Contains the most recent version for each Chrome release channel (Stable, Beta, Dev, Canary).
117#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
118pub struct LastKnownGoodVersions {
119    /// When this data was last updated.
120    #[serde(with = "time::serde::rfc3339")]
121    pub timestamp: time::OffsetDateTime,
122
123    /// The latest known good version for each release channel.
124    pub channels: HashMap<Channel, VersionInChannel>,
125}
126
127impl LastKnownGoodVersions {
128    /// Fetches the last known good versions from the Chrome for Testing API.
129    ///
130    /// Returns the most recent version for each Chrome release channel (Stable, Beta, Dev, Canary).
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the HTTP request or deserialization fails.
135    pub async fn fetch(client: reqwest::Client) -> Result<Self> {
136        Self::fetch_with_base_url(client, API_BASE_URL.clone()).await
137    }
138
139    /// Fetches from a custom base URL (useful for testing).
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if the HTTP request or deserialization fails.
144    pub async fn fetch_with_base_url(
145        client: reqwest::Client,
146        base_url: reqwest::Url,
147    ) -> Result<LastKnownGoodVersions> {
148        let last_known_good_versions = client
149            .get(base_url.join(LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)?)
150            .send()
151            .await?
152            .json::<Self>()
153            .await?;
154        Ok(last_known_good_versions)
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::api::Download;
161    use crate::api::channel::Channel;
162    use crate::api::last_known_good_versions::{
163        Downloads, LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH, LastKnownGoodVersions,
164        VersionInChannel,
165    };
166    use crate::api::platform::Platform;
167    use crate::api::version::Version;
168    use assertr::prelude::*;
169    use std::collections::HashMap;
170    use time::macros::datetime;
171    use url::Url;
172
173    #[tokio::test]
174    async fn can_request_from_real_world_endpoint() {
175        let result = LastKnownGoodVersions::fetch(reqwest::Client::new()).await;
176        assert_that!(result).is_ok();
177    }
178
179    //noinspection DuplicatedCode
180    #[tokio::test]
181    async fn can_query_last_known_good_versions_api_endpoint_and_deserialize_response() {
182        let mut server = mockito::Server::new_async().await;
183
184        let _mock = server
185            .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
186            .with_status(200)
187            .with_header("content-type", "application/json")
188            .with_body(include_str!(
189                "./../../test-data/last_known_good_versions_test_response.json"
190            ))
191            .create();
192
193        let url: Url = server.url().parse().unwrap();
194
195        let data = LastKnownGoodVersions::fetch_with_base_url(reqwest::Client::new(), url)
196            .await
197            .unwrap();
198
199        assert_that!(data).is_equal_to(LastKnownGoodVersions {
200            timestamp: datetime!(2025-01-17 10:09:31.683 UTC),
201            channels: HashMap::from([
202                (
203                    Channel::Stable,
204                    VersionInChannel {
205                        channel: Channel::Stable,
206                        version: Version { major: 132, minor: 0, patch: 6834, build: 83 },
207                        revision: String::from("1381561"),
208                        downloads: Downloads {
209                            chrome: vec![
210                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-linux64.zip") },
211                                Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chrome-mac-arm64.zip") },
212                                Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chrome-mac-x64.zip") },
213                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-win32.zip") },
214                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-win64.zip") },
215                            ],
216                            chromedriver: vec![
217                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chromedriver-linux64.zip") },
218                                Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chromedriver-mac-arm64.zip") },
219                                Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chromedriver-mac-x64.zip") },
220                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chromedriver-win32.zip") },
221                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chromedriver-win64.zip") },
222                            ],
223                            chrome_headless_shell: vec![
224                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-headless-shell-linux64.zip") },
225                                Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
226                                Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chrome-headless-shell-mac-x64.zip") },
227                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-headless-shell-win32.zip") },
228                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-headless-shell-win64.zip") },
229                            ],
230                        },
231                    }
232                ),
233                (Channel::Beta, VersionInChannel {
234                    channel: Channel::Beta,
235                    version: Version { major: 133, minor: 0, patch: 6943, build: 16 },
236                    revision: String::from("1402768"),
237                    downloads: Downloads {
238                        chrome: vec![
239                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-linux64.zip") },
240                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chrome-mac-arm64.zip") },
241                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chrome-mac-x64.zip") },
242                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-win32.zip") },
243                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-win64.zip") },
244                        ],
245                        chromedriver: vec![
246                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chromedriver-linux64.zip") },
247                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chromedriver-mac-arm64.zip") },
248                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chromedriver-mac-x64.zip") },
249                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chromedriver-win32.zip") },
250                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chromedriver-win64.zip") },
251                        ],
252                        chrome_headless_shell: vec![
253                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-headless-shell-linux64.zip") },
254                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
255                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chrome-headless-shell-mac-x64.zip") },
256                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-headless-shell-win32.zip") },
257                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-headless-shell-win64.zip") },
258                        ],
259                    },
260                }),
261                (Channel::Dev, VersionInChannel {
262                    channel: Channel::Dev,
263                    version: Version { major: 134, minor: 0, patch: 6958, build: 2 },
264                    revision: String::from("1406477"),
265                    downloads: Downloads {
266                        chrome: vec![
267                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-linux64.zip") },
268                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chrome-mac-arm64.zip") },
269                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chrome-mac-x64.zip") },
270                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-win32.zip") },
271                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-win64.zip") },
272                        ],
273                        chromedriver: vec![
274                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chromedriver-linux64.zip") },
275                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chromedriver-mac-arm64.zip") },
276                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chromedriver-mac-x64.zip") },
277                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chromedriver-win32.zip") },
278                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chromedriver-win64.zip") },
279                        ],
280                        chrome_headless_shell: vec![
281                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-headless-shell-linux64.zip") },
282                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
283                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chrome-headless-shell-mac-x64.zip") },
284                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-headless-shell-win32.zip") },
285                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-headless-shell-win64.zip") },
286                        ],
287                    },
288                }),
289                (Channel::Canary, VersionInChannel {
290                    channel: Channel::Canary,
291                    version: Version { major: 134, minor: 0, patch: 6962, build: 0 },
292                    revision: String::from("1407692"),
293                    downloads: Downloads {
294                        chrome: vec![
295                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-linux64.zip") },
296                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chrome-mac-arm64.zip") },
297                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chrome-mac-x64.zip") },
298                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-win32.zip") },
299                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-win64.zip") },
300                        ],
301                        chromedriver: vec![
302                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chromedriver-linux64.zip") },
303                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chromedriver-mac-arm64.zip") },
304                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chromedriver-mac-x64.zip") },
305                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chromedriver-win32.zip") },
306                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chromedriver-win64.zip") },
307                        ],
308                        chrome_headless_shell: vec![
309                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-headless-shell-linux64.zip") },
310                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
311                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chrome-headless-shell-mac-x64.zip") },
312                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-headless-shell-win32.zip") },
313                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-headless-shell-win64.zip") },
314                        ],
315                    },
316                })
317            ]),
318        });
319    }
320}