Skip to content

Commit 7dc4315

Browse files
shossain786claude
andauthored
feat(browser): add browser.version to pin local Chrome/Firefox runs (#51) (#104)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent e95d271 commit 7dc4315

6 files changed

Lines changed: 87 additions & 0 deletions

File tree

‎docs-site/docs/configuration.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ browser:
4949
name: chrome # chrome | firefox | edge | safari
5050
headless: false # true in CI (auto-forced when CI detected)
5151
lifecycle: per-test # per-test (default) | per-suite
52+
# version: "120.0.6099.109" # optional: pin local Chrome/Firefox to an exact build
5253

5354
# Optional: extra browser arguments
5455
arguments:
@@ -184,6 +185,13 @@ The browser to use. Selenium Manager downloads the matching driver automatically
184185
### `headless`
185186
Runs the browser without a visible window. Automatically forced to `true` when a CI environment is detected (GitHub Actions, Jenkins, etc.).
186187

188+
### `version`
189+
Pins a local Chrome or Firefox run to an exact browser version, for when "passes on my
190+
laptop, fails in CI" is a browser-version mismatch. Passed to Selenium Manager, which resolves
191+
and downloads the matching build. Optional — omit it to use whatever browser is installed.
192+
Ignored by `remote`, `browserstack` and `saucelabs` modes (those use their own
193+
`browserVersion`).
194+
187195
### `lifecycle`
188196
Controls when the WebDriver session is closed.
189197

‎src/main/java/com/seleniumboot/config/SeleniumBootConfig.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ public static final class Browser {
110110
private boolean failOnConsoleErrors = false;
111111
private List<String> matrix = Collections.emptyList();
112112
private String device; // optional device profile name, e.g. "iPhone 14"
113+
private String version; // optional exact browser version for local Chrome/Firefox runs
114+
115+
public String getVersion() { return version; }
116+
public void setVersion(String version) { this.version = version; }
113117

114118
public String getDevice() { return device; }
115119
public void setDevice(String device) { this.device = device; }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.seleniumboot.driver;
2+
3+
import org.openqa.selenium.remote.AbstractDriverOptions;
4+
5+
/**
6+
* Applies {@code browser.version} to local driver options. Selenium Manager resolves
7+
* (and downloads if needed) the matching browser build. Omitted or blank leaves the options
8+
* untouched, so the browser installed on the machine is used.
9+
*/
10+
final class BrowserVersionPin {
11+
12+
private BrowserVersionPin() {}
13+
14+
static void apply(AbstractDriverOptions<?> options, String version) {
15+
if (version != null && !version.isBlank()) {
16+
options.setBrowserVersion(version.trim());
17+
}
18+
}
19+
}

‎src/main/java/com/seleniumboot/driver/LocalChromeDriverProvider.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public WebDriver createDriver() {
3232
capabilities.forEach(options::setCapability);
3333
}
3434

35+
BrowserVersionPin.apply(options, config.getBrowser().getVersion());
36+
3537
if (config.getBrowser().isHeadless()) {
3638
options.addArguments("--headless=new");
3739
}

‎src/main/java/com/seleniumboot/driver/LocalFirefoxDriverProvider.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public WebDriver createDriver() {
3535
capabilities.forEach(options::setCapability);
3636
}
3737

38+
BrowserVersionPin.apply(options, config.getBrowser().getVersion());
39+
3840
if (config.getBrowser().isHeadless()) {
3941
options.addArguments("-headless");
4042
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.seleniumboot.unit;
2+
3+
import com.seleniumboot.config.SeleniumBootConfig;
4+
import org.openqa.selenium.chrome.ChromeOptions;
5+
import org.openqa.selenium.firefox.FirefoxOptions;
6+
import org.testng.annotations.Test;
7+
8+
import static org.testng.Assert.assertEquals;
9+
import static org.testng.Assert.assertNull;
10+
11+
/** {@code browser.version} is passed to local driver options only when set. */
12+
public class BrowserVersionPinTest {
13+
14+
private static void apply(Object options, String version) throws Exception {
15+
Class<?> pin = Class.forName("com.seleniumboot.driver.BrowserVersionPin");
16+
var m = pin.getDeclaredMethod("apply", org.openqa.selenium.remote.AbstractDriverOptions.class, String.class);
17+
m.setAccessible(true);
18+
m.invoke(null, options, version);
19+
}
20+
21+
@Test
22+
public void chrome_versionSet_isPassedThrough() throws Exception {
23+
ChromeOptions o = new ChromeOptions();
24+
apply(o, "120.0.6099.109");
25+
assertEquals(o.getBrowserVersion(), "120.0.6099.109");
26+
}
27+
28+
@Test
29+
public void firefox_versionSet_isPassedThrough() throws Exception {
30+
FirefoxOptions o = new FirefoxOptions();
31+
apply(o, " 121.0 ");
32+
assertEquals(o.getBrowserVersion(), "121.0");
33+
}
34+
35+
@Test
36+
public void nullOrBlank_leavesVersionUnset() throws Exception {
37+
ChromeOptions a = new ChromeOptions();
38+
apply(a, null);
39+
ChromeOptions b = new ChromeOptions();
40+
apply(b, " ");
41+
assertEquals(a.getBrowserVersion(), new ChromeOptions().getBrowserVersion());
42+
assertEquals(b.getBrowserVersion(), new ChromeOptions().getBrowserVersion());
43+
}
44+
45+
@Test
46+
public void config_defaultsToNull_andRoundTrips() {
47+
SeleniumBootConfig.Browser b = new SeleniumBootConfig.Browser();
48+
assertNull(b.getVersion());
49+
b.setVersion("120");
50+
assertEquals(b.getVersion(), "120");
51+
}
52+
}

0 commit comments

Comments
 (0)