Skip to main content

chrome_for_testing/api/
known_good_versions.rs

1use crate::api::platform::Platform;
2use crate::api::version::Version;
3use crate::api::{API_BASE_URL, Download, DownloadsByPlatform, fetch_endpoint};
4use serde::{Deserialize, Serialize};
5
6/// JSON Example:
7/// ```json
8/// {
9///     "version": "115.0.5763.0",
10///     "revision": "1141961",
11///     "downloads": {
12///         "chrome": [
13///             {
14///                 "platform": "linux64",
15///                 "url": "https://.../chrome-linux64.zip"
16///             },
17///             ...
18///         ],
19///         "chromedriver": [ /* <- Some versions don't have this field! */
20///             {
21///                 "platform": "linux64",
22///                 "url": "https://.../chromedriver-linux64.zip"
23///             },
24///             ...
25///         ],
26///         "chrome-headless-shell": [ /* <- Some versions don't have this field! */
27///             {
28///                 "platform": "linux64",
29///                 "url": "https://.../chrome-headless-shell-linux64.zip"
30///             },
31///             ...
32///         ],
33///     }
34/// },
35/// ```
36const KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH: &str =
37    "/chrome-for-testing/known-good-versions-with-downloads.json";
38
39/// Download links for `Chrome`, `ChromeDriver`, and `Chrome Headless Shell` binaries.
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Downloads {
42    /// Download links for Chrome binaries for various platforms.
43    pub chrome: Vec<Download>,
44
45    /// Download links for `ChromeDriver` binaries for various platforms.
46    /// Note: Some older Chrome versions may not have `ChromeDriver` downloads available!
47    pub chromedriver: Option<Vec<Download>>,
48
49    /// Download links for Chrome Headless Shell binaries for various platforms.
50    /// Note: Some older Chrome versions may not have Chrome Headless Shell downloads available!
51    #[serde(rename = "chrome-headless-shell")]
52    pub chrome_headless_shell: Option<Vec<Download>>,
53}
54
55impl Downloads {
56    /// Returns the Chrome download entry for the given platform, if available.
57    #[must_use]
58    pub fn chrome_for_platform(&self, platform: Platform) -> Option<&Download> {
59        self.chrome.for_platform(platform)
60    }
61
62    /// Returns the `ChromeDriver` download entry for the given platform, if available.
63    #[must_use]
64    pub fn chromedriver_for_platform(&self, platform: Platform) -> Option<&Download> {
65        self.chromedriver.as_deref()?.for_platform(platform)
66    }
67
68    /// Returns the Chrome Headless Shell download entry for the given platform, if available.
69    #[must_use]
70    pub fn chrome_headless_shell_for_platform(&self, platform: Platform) -> Option<&Download> {
71        self.chrome_headless_shell
72            .as_deref()?
73            .for_platform(platform)
74    }
75}
76
77/// An entry of the "known good versions" API response, representing one version.
78///
79/// No `Channel` information is provided.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct VersionWithoutChannel {
82    /// The version identifier.
83    pub version: Version,
84
85    /// The Chrome revision number.
86    pub revision: String,
87
88    /// Available downloads for this version.
89    pub downloads: Downloads,
90}
91
92/// Response structure for the "known good versions" API endpoint.
93///
94/// Contains a comprehensive list of Chrome versions that have been tested and verified to work.
95/// Unlike the "last known good versions" API, this includes all historical versions without
96/// channel assignments.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct KnownGoodVersions {
99    /// When this data was last updated.
100    #[serde(with = "time::serde::rfc3339")]
101    pub timestamp: time::OffsetDateTime,
102
103    /// List of all known good Chrome versions.
104    pub versions: Vec<VersionWithoutChannel>,
105}
106
107impl KnownGoodVersions {
108    /// Fetches the list of all known good Chrome versions from the Chrome for Testing API.
109    ///
110    /// Returns a comprehensive list of Chrome versions that have been tested and verified to work.
111    /// Unlike the "last known good versions" API, this includes all historical versions without
112    /// channel assignments.
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the HTTP request fails, the response has an unsuccessful status, or
117    /// deserialization fails.
118    pub async fn fetch(client: &reqwest::Client) -> crate::Result<Self> {
119        Self::fetch_with_base_url(client, &API_BASE_URL).await
120    }
121
122    /// Fetches from a custom base URL (useful for testing).
123    ///
124    /// # Errors
125    ///
126    /// Returns an error if the HTTP request fails, the response has an unsuccessful status, or
127    /// deserialization fails.
128    pub async fn fetch_with_base_url(
129        client: &reqwest::Client,
130        base_url: &reqwest::Url,
131    ) -> crate::Result<Self> {
132        fetch_endpoint::<Self>(
133            client,
134            base_url,
135            KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH,
136            "KnownGoodVersions",
137        )
138        .await
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::api::Download;
146    use crate::api::known_good_versions::KnownGoodVersions;
147    use crate::api::platform::Platform;
148    use crate::api::version::Version;
149    use crate::error::Error;
150    use assertr::prelude::*;
151    use time::macros::datetime;
152    use url::Url;
153
154    // This test should not be `#[ignore]`, even though it hits the Chrome For Testing API.
155    #[tokio::test]
156    async fn can_request_from_real_world_endpoint() {
157        let result = KnownGoodVersions::fetch(&reqwest::Client::new()).await;
158        assert_that!(result).is_ok();
159    }
160
161    //noinspection DuplicatedCode
162    #[tokio::test]
163    async fn can_query_known_good_versions_api_endpoint_and_deserialize_response() {
164        let mut server = mockito::Server::new_async().await;
165        let _mock = server
166            .mock("GET", KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
167            .with_status(200)
168            .with_header("content-type", "application/json")
169            .with_body(include_str!(
170                "./../../test-data/known_good_versions_with_downloads_test_response.json"
171            ))
172            .create();
173
174        let mock_url: Url = server.url().parse().unwrap();
175
176        let data = KnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &mock_url)
177            .await
178            .unwrap();
179
180        assert_that!(data).is_equal_to(KnownGoodVersions {
181            timestamp: datetime!(2026-04-13 08:53:52.847 UTC),
182            versions: vec![
183                VersionWithoutChannel {
184                    version: Version { major: 113, minor: 0, patch: 5672, build: 0 },
185                    revision: String::from("1121455"),
186                    downloads: Downloads {
187                        chrome: vec![
188                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/linux64/chrome-linux64.zip") },
189                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/mac-arm64/chrome-mac-arm64.zip") },
190                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/mac-x64/chrome-mac-x64.zip") },
191                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/win32/chrome-win32.zip") },
192                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/win64/chrome-win64.zip") },
193                        ],
194                        chromedriver: None,
195                        chrome_headless_shell: None,
196                    },
197                },
198                VersionWithoutChannel {
199                    version: Version { major: 149, minor: 0, patch: 7789, build: 0 },
200                    revision: String::from("1613465"),
201                    downloads: Downloads {
202                        chrome: vec![
203                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chrome-linux64.zip") },
204                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chrome-mac-arm64.zip") },
205                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chrome-mac-x64.zip") },
206                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chrome-win32.zip") },
207                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chrome-win64.zip") },
208                        ],
209                        chromedriver: Some(vec![
210                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chromedriver-linux64.zip") },
211                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chromedriver-mac-arm64.zip") },
212                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chromedriver-mac-x64.zip") },
213                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chromedriver-win32.zip") },
214                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chromedriver-win64.zip") },
215                        ]),
216                        chrome_headless_shell: Some(vec![
217                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chrome-headless-shell-linux64.zip") },
218                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
219                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chrome-headless-shell-mac-x64.zip") },
220                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chrome-headless-shell-win32.zip") },
221                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chrome-headless-shell-win64.zip") },
222                        ]),
223                    },
224                },
225            ],
226        });
227    }
228
229    #[tokio::test]
230    async fn unsuccessful_http_status_is_reported_as_request_error() {
231        let mut server = mockito::Server::new_async().await;
232        let _mock = server
233            .mock("GET", KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
234            .with_status(500)
235            .with_header("content-type", "application/json")
236            .with_body(include_str!(
237                "./../../test-data/known_good_versions_with_downloads_test_response.json"
238            ))
239            .create();
240
241        let url: Url = server.url().parse().unwrap();
242
243        let err = KnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
244            .await
245            .unwrap_err();
246
247        let Error::Request(request_error) = err.current_context() else {
248            panic!("expected request error, got: {:?}", err.current_context());
249        };
250
251        assert_that!(request_error.status())
252            .is_equal_to(Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR));
253    }
254}