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, Serialize};
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, Serialize, 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, Serialize, 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, Serialize, 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).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    /// Returns the version info for the given channel, if present.
158    #[must_use]
159    pub fn channel(&self, channel: Channel) -> Option<&VersionInChannel> {
160        self.channels.get(&channel)
161    }
162
163    /// Returns the stable channel version info, if present.
164    #[must_use]
165    pub fn stable(&self) -> Option<&VersionInChannel> {
166        self.channel(Channel::Stable)
167    }
168
169    /// Returns the beta channel version info, if present.
170    #[must_use]
171    pub fn beta(&self) -> Option<&VersionInChannel> {
172        self.channel(Channel::Beta)
173    }
174
175    /// Returns the dev channel version info, if present.
176    #[must_use]
177    pub fn dev(&self) -> Option<&VersionInChannel> {
178        self.channel(Channel::Dev)
179    }
180
181    /// Returns the canary channel version info, if present.
182    #[must_use]
183    pub fn canary(&self) -> Option<&VersionInChannel> {
184        self.channel(Channel::Canary)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use crate::api::Download;
191    use crate::api::channel::Channel;
192    use crate::api::last_known_good_versions::{
193        Downloads, LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH, LastKnownGoodVersions,
194        VersionInChannel,
195    };
196    use crate::api::platform::Platform;
197    use crate::api::version::Version;
198    use assertr::prelude::*;
199    use std::collections::HashMap;
200    use time::macros::datetime;
201    use url::Url;
202
203    #[tokio::test]
204    async fn can_request_from_real_world_endpoint() {
205        let result = LastKnownGoodVersions::fetch(&reqwest::Client::new()).await;
206        assert_that!(result).is_ok();
207    }
208
209    //noinspection DuplicatedCode
210    #[tokio::test]
211    async fn can_query_last_known_good_versions_api_endpoint_and_deserialize_response() {
212        let mut server = mockito::Server::new_async().await;
213
214        let _mock = server
215            .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
216            .with_status(200)
217            .with_header("content-type", "application/json")
218            .with_body(include_str!(
219                "./../../test-data/last_known_good_versions_test_response.json"
220            ))
221            .create();
222
223        let url: Url = server.url().parse().unwrap();
224
225        let data = LastKnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
226            .await
227            .unwrap();
228
229        assert_that!(data).is_equal_to(LastKnownGoodVersions {
230            timestamp: datetime!(2025-01-17 10:09:31.683 UTC),
231            channels: HashMap::from([
232                (
233                    Channel::Stable,
234                    VersionInChannel {
235                        channel: Channel::Stable,
236                        version: Version { major: 132, minor: 0, patch: 6834, build: 83 },
237                        revision: String::from("1381561"),
238                        downloads: Downloads {
239                            chrome: vec![
240                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-linux64.zip") },
241                                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") },
242                                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") },
243                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-win32.zip") },
244                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-win64.zip") },
245                            ],
246                            chromedriver: vec![
247                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chromedriver-linux64.zip") },
248                                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") },
249                                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") },
250                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chromedriver-win32.zip") },
251                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chromedriver-win64.zip") },
252                            ],
253                            chrome_headless_shell: vec![
254                                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") },
255                                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") },
256                                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") },
257                                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") },
258                                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") },
259                            ],
260                        },
261                    }
262                ),
263                (Channel::Beta, VersionInChannel {
264                    channel: Channel::Beta,
265                    version: Version { major: 133, minor: 0, patch: 6943, build: 16 },
266                    revision: String::from("1402768"),
267                    downloads: Downloads {
268                        chrome: vec![
269                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-linux64.zip") },
270                            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") },
271                            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") },
272                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-win32.zip") },
273                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-win64.zip") },
274                        ],
275                        chromedriver: vec![
276                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chromedriver-linux64.zip") },
277                            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") },
278                            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") },
279                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chromedriver-win32.zip") },
280                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chromedriver-win64.zip") },
281                        ],
282                        chrome_headless_shell: vec![
283                            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") },
284                            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") },
285                            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") },
286                            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") },
287                            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") },
288                        ],
289                    },
290                }),
291                (Channel::Dev, VersionInChannel {
292                    channel: Channel::Dev,
293                    version: Version { major: 134, minor: 0, patch: 6958, build: 2 },
294                    revision: String::from("1406477"),
295                    downloads: Downloads {
296                        chrome: vec![
297                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-linux64.zip") },
298                            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") },
299                            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") },
300                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-win32.zip") },
301                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-win64.zip") },
302                        ],
303                        chromedriver: vec![
304                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chromedriver-linux64.zip") },
305                            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") },
306                            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") },
307                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chromedriver-win32.zip") },
308                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chromedriver-win64.zip") },
309                        ],
310                        chrome_headless_shell: vec![
311                            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") },
312                            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") },
313                            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") },
314                            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") },
315                            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") },
316                        ],
317                    },
318                }),
319                (Channel::Canary, VersionInChannel {
320                    channel: Channel::Canary,
321                    version: Version { major: 134, minor: 0, patch: 6962, build: 0 },
322                    revision: String::from("1407692"),
323                    downloads: Downloads {
324                        chrome: vec![
325                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-linux64.zip") },
326                            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") },
327                            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") },
328                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-win32.zip") },
329                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-win64.zip") },
330                        ],
331                        chromedriver: vec![
332                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chromedriver-linux64.zip") },
333                            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") },
334                            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") },
335                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chromedriver-win32.zip") },
336                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chromedriver-win64.zip") },
337                        ],
338                        chrome_headless_shell: vec![
339                            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") },
340                            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") },
341                            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") },
342                            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") },
343                            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") },
344                        ],
345                    },
346                })
347            ]),
348        });
349    }
350}