chrome_for_testing/api/
last_known_good_versions.rs

1use crate::api::channel::Channel;
2use crate::api::version::Version;
3use crate::api::{Download, API_BASE_URL};
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    pub async fn fetch(client: reqwest::Client) -> Result<Self> {
132        Self::fetch_with_base_url(client, API_BASE_URL.clone()).await
133    }
134
135    pub async fn fetch_with_base_url(
136        client: reqwest::Client,
137        base_url: reqwest::Url,
138    ) -> Result<LastKnownGoodVersions> {
139        let last_known_good_versions = client
140            .get(base_url.join(LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)?)
141            .send()
142            .await?
143            .json::<Self>()
144            .await?;
145        Ok(last_known_good_versions)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use crate::api::channel::Channel;
152    use crate::api::last_known_good_versions::{
153        Downloads, LastKnownGoodVersions, VersionInChannel,
154        LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH,
155    };
156    use crate::api::platform::Platform;
157    use crate::api::version::Version;
158    use crate::api::Download;
159    use assertr::prelude::*;
160    use std::collections::HashMap;
161    use time::macros::datetime;
162    use url::Url;
163
164    #[tokio::test]
165    async fn can_request_from_real_world_endpoint() {
166        let result = LastKnownGoodVersions::fetch(reqwest::Client::new()).await;
167        assert_that(result).is_ok();
168    }
169
170    //noinspection DuplicatedCode
171    #[tokio::test]
172    async fn can_query_last_known_good_versions_api_endpoint_and_deserialize_response() {
173        let mut server = mockito::Server::new_async().await;
174
175        let _mock = server
176            .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
177            .with_status(200)
178            .with_header("content-type", "application/json")
179            .with_body(include_str!(
180                "./../../test-data/last_known_good_versions_test_response.json"
181            ))
182            .create();
183
184        let url: Url = server.url().parse().unwrap();
185
186        let data = LastKnownGoodVersions::fetch_with_base_url(reqwest::Client::new(), url)
187            .await
188            .unwrap();
189
190        assert_that(data).is_equal_to(LastKnownGoodVersions {
191            timestamp: datetime!(2025-01-17 10:09:31.683 UTC),
192            channels: HashMap::from([
193                (
194                    Channel::Stable,
195                    VersionInChannel {
196                        channel: Channel::Stable,
197                        version: Version { major: 132, minor: 0, patch: 6834, build: 83 },
198                        revision: String::from("1381561"),
199                        downloads: Downloads {
200                            chrome: vec![
201                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-linux64.zip") },
202                                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") },
203                                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") },
204                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-win32.zip") },
205                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-win64.zip") },
206                            ],
207                            chromedriver: vec![
208                                Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chromedriver-linux64.zip") },
209                                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") },
210                                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") },
211                                Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chromedriver-win32.zip") },
212                                Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chromedriver-win64.zip") },
213                            ],
214                            chrome_headless_shell: vec![
215                                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") },
216                                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") },
217                                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") },
218                                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") },
219                                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") },
220                            ],
221                        },
222                    }
223                ),
224                (Channel::Beta, VersionInChannel {
225                    channel: Channel::Beta,
226                    version: Version { major: 133, minor: 0, patch: 6943, build: 16 },
227                    revision: String::from("1402768"),
228                    downloads: Downloads {
229                        chrome: vec![
230                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-linux64.zip") },
231                            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") },
232                            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") },
233                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-win32.zip") },
234                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-win64.zip") },
235                        ],
236                        chromedriver: vec![
237                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chromedriver-linux64.zip") },
238                            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") },
239                            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") },
240                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chromedriver-win32.zip") },
241                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chromedriver-win64.zip") },
242                        ],
243                        chrome_headless_shell: vec![
244                            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") },
245                            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") },
246                            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") },
247                            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") },
248                            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") },
249                        ],
250                    },
251                }),
252                (Channel::Dev, VersionInChannel {
253                    channel: Channel::Dev,
254                    version: Version { major: 134, minor: 0, patch: 6958, build: 2 },
255                    revision: String::from("1406477"),
256                    downloads: Downloads {
257                        chrome: vec![
258                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-linux64.zip") },
259                            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") },
260                            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") },
261                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-win32.zip") },
262                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-win64.zip") },
263                        ],
264                        chromedriver: vec![
265                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chromedriver-linux64.zip") },
266                            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") },
267                            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") },
268                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chromedriver-win32.zip") },
269                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chromedriver-win64.zip") },
270                        ],
271                        chrome_headless_shell: vec![
272                            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") },
273                            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") },
274                            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") },
275                            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") },
276                            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") },
277                        ],
278                    },
279                }),
280                (Channel::Canary, VersionInChannel {
281                    channel: Channel::Canary,
282                    version: Version { major: 134, minor: 0, patch: 6962, build: 0 },
283                    revision: String::from("1407692"),
284                    downloads: Downloads {
285                        chrome: vec![
286                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-linux64.zip") },
287                            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") },
288                            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") },
289                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-win32.zip") },
290                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-win64.zip") },
291                        ],
292                        chromedriver: vec![
293                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chromedriver-linux64.zip") },
294                            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") },
295                            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") },
296                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chromedriver-win32.zip") },
297                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chromedriver-win64.zip") },
298                        ],
299                        chrome_headless_shell: vec![
300                            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") },
301                            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") },
302                            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") },
303                            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") },
304                            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") },
305                        ],
306                    },
307                })
308            ]),
309        });
310    }
311}