Selenium WebDriver 403 Forbidden Error
Problem Statement
When running Selenium WebDriver tests in Azure DevOps (or similar remote environments), you may encounter a java.io.IOException: Invalid Status code=403 text=Forbidden
error. This typically occurs after Chrome updates to version 111+ and manifests in the stack trace as:
org.openqa.selenium.remote.http.WebSocket$Listener onError
WARNING: Invalid Status code=403 text=Forbidden
java.io.IOException: Invalid Status code=403 text=Forbidden
...
The key indicators:
- Tests fail in CI/CD pipelines (e.g., Azure DevOps) but work locally
- ChromeDriver and Chrome versions are 111+
- Error involves WebSocket communication rejection (
devtools_http_handler
)
Root cause:
Chrome's tightened security in v111 blocks WebSocket connections from localhost
origins unless explicitly allowed.
Best Solutions
Choose the most appropriate solution for your environment.
Solution 1: Allow All WebSocket Origins (ChromeOptions)
Add --remote-allow-origins=*
to bypass origin restrictions.
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*"); // Fixes 403 error
WebDriver driver = new ChromeDriver(options);
Security Note
Using *
allows all origins. For production environments, specify exact origins (e.g., --remote-allow-origins=http://localhost:8080
).
Solution 2: Upgrade Selenium for JDK 8+ Projects
Update to Selenium 4.9.0+ to resolve the issue natively (no flags needed).
<!-- Maven dependency -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.9.0</version> <!-- Or later -->
</dependency>
Solution 3: Use Java 11+ HTTP Client
For Selenium 4.5+ with Java 11+, switch to the JDK HTTP client.
- Add the client dependency:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-http-jdk-client</artifactId>
<version>4.8.1</version> <!-- Match your Selenium version -->
</dependency>
- Set the HTTP factory in your code:
System.setProperty("webdriver.http.factory", "jdk-http-client"); // Before driver init
ChromeOptions options = new ChromeOptions();
WebDriver driver = new ChromeDriver(options);
Solution 4: Update Selenide (For Selenide Projects)
Upgrade to Selenide 6.12.2+ if you use this wrapper.
<!-- Maven -->
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>6.12.2</version> <!-- Or later -->
</dependency>
Why These Solutions Work
ChromeOptions Flag
Explicitly allows WebSocket connections fromlocalhost
, which Chrome 111 now blocks by default.- Origin: ChromeDriver logs show:
Rejected WebSocket connection. Use --remote-allow-origins...
- Trade-off: Convenient for tests but slightly reduces security.
- Origin: ChromeDriver logs show:
Selenium 4.9.0+
Addresses compatibility with Chrome’s new security model internally.
✅ Use if you can update dependencies freely.JDK HTTP Client
Replaces Selenium’s default HTTP client with Java’s built-in client.
✅ Better HTTPS/CORS handling in Java 11 environments.Selenide Update
Selenide bundles Selenium fixes. Version 6.12.2 incorporates these security patches.
Common Pitfalls to Avoid
Version Conflicts
Ensure Chrome, ChromeDriver, and Selenium versions are compatible. Use ChromeDriver’s compatibility matrix.- Test locally with
System.out.println(ChromeDriver.class.getPackage().getName())
.
- Test locally with
Outdated WebDriverManager
Always update WebDriverManager for version resolution:javaWebDriverManager.chromedriver().setup(); // Before creating driver instance
Hardcoded Driver Paths
Use environment variables or WebDriverManager to handle paths in Azure DevOps.
Verification Steps
- Confirm Chrome version in Azure logs:bash
google-chrome --version # In pipeline script
- Validate ChromeDriver version alignment:java
@BeforeTest public void setup() { System.setProperty("webdriver.http.factory", "jdk-http-client"); // If using Solution 3 WebDriverManager.chromedriver().setup(); ChromeOptions options = new ChromeOptions(); if (isAzurePipeline()) options.addArguments("--remote-allow-origins=*"); driver = new ChromeDriver(options); }