Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Unreleased

- **Fix: `@PreCondition` session restore on a fresh browser** — the cached cookies and localStorage were silently dropped when the next test started on `about:blank`, so only the first test per thread was logged in. Restore now navigates to the origin the session was captured on first.

- **Trace shows browser console errors** — when `browser.captureConsoleErrors: true`, the JS errors collected for a test now appear in its trace file under "Console Errors", next to the steps and failure details, so a shared trace includes what the browser logged. No change when the option is off. (#130)

- **Network mocking on Firefox via WebDriver BiDi** — `networkMock().stub(...)` now uses WebDriver BiDi network interception under the hood when supported by the driver (including Firefox and BiDi-enabled drivers), falling back to Chrome DevTools Protocol (CDP) for Chromium-based drivers. The existing stubbing API (`returnJson`, `returnBody`, `returnStatus`, `delay`, `clear`) works identically across Chrome and Firefox. (#196)
Expand Down
26 changes: 23 additions & 3 deletions src/main/java/com/seleniumboot/precondition/SessionCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ final class SessionCache {
static final class SavedSession {
final Set<Cookie> cookies;
final Map<String, String> localStorage;
final String origin; // scheme://host[:port] the session was captured on

SavedSession(Set<Cookie> cookies, Map<String, String> localStorage) {
SavedSession(Set<Cookie> cookies, Map<String, String> localStorage, String origin) {
this.cookies = cookies;
this.localStorage = localStorage;
this.origin = origin;
}

boolean isEmpty() {
Expand All @@ -40,17 +42,23 @@ private SessionCache() {}
static void store(String conditionName, WebDriver driver) {
Set<Cookie> cookies = driver.manage().getCookies();
Map<String, String> localStorage = captureLocalStorage(driver);
cache.get().put(conditionName, new SavedSession(cookies, localStorage));
cache.get().put(conditionName, new SavedSession(cookies, localStorage, originOf(driver.getCurrentUrl())));
}

/**
* Restores cookies and localStorage from cache into the current browser session.
* Navigates to the current URL to apply cookies before restoring localStorage.
* A browser can only accept cookies and localStorage for the origin it is on, and a fresh
* session starts on {@code about:blank}, so it first navigates to the origin the session
* was captured on.
*/
static void restore(String conditionName, WebDriver driver) {
SavedSession session = cache.get().get(conditionName);
if (session == null) return;

if (session.origin != null && !session.origin.equals(originOf(driver.getCurrentUrl()))) {
driver.get(session.origin);
}

driver.manage().deleteAllCookies();
for (Cookie cookie : session.cookies) {
try { driver.manage().addCookie(cookie); } catch (Exception ignored) {}
Expand Down Expand Up @@ -105,6 +113,18 @@ private static void restoreLocalStorage(WebDriver driver, Map<String, String> it
} catch (Exception ignored) {}
}

/** Returns scheme://host[:port] of an http(s) URL, or null for anything else (about:blank, data:). */
private static String originOf(String url) {
try {
java.net.URI u = java.net.URI.create(url);
if (u.getScheme() == null || u.getHost() == null
|| !(u.getScheme().equals("http") || u.getScheme().equals("https"))) return null;
return u.getScheme() + "://" + u.getHost() + (u.getPort() == -1 ? "" : ":" + u.getPort());
} catch (Exception e) {
return null;
}
}

private static String jsString(String value) {
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.seleniumboot.precondition;

import com.sun.net.httpserver.HttpServer;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

import static org.testng.Assert.assertNotNull;

/**
* Live check that a cached session is restored into a browser that is not on the app origin
* (a fresh driver starts on about:blank, where {@code addCookie} is rejected).
* Skips when Chrome is not available.
*/
public class SessionCacheLiveTest {

private HttpServer server;
private String base;

@BeforeClass
public void startServer() throws Exception {
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", ex -> {
byte[] body = "<html><body>ok</body></html>".getBytes(StandardCharsets.UTF_8);
ex.getResponseHeaders().set("Content-Type", "text/html");
if (ex.getRequestURI().getPath().equals("/login")) {
ex.getResponseHeaders().add("Set-Cookie", "sso=abc123; Path=/");
}
ex.sendResponseHeaders(200, body.length);
ex.getResponseBody().write(body);
ex.close();
});
server.start();
base = "http://127.0.0.1:" + server.getAddress().getPort();
}

@AfterClass(alwaysRun = true)
public void stopServer() {
if (server != null) server.stop(0);
}

@Test
public void restore_fromAboutBlank_restoresCookies() {
WebDriver driver;
try {
ChromeOptions o = new ChromeOptions();
o.addArguments("--headless=new", "--no-sandbox");
driver = new ChromeDriver(o);
} catch (Throwable t) {
throw new SkipException("Chrome not available: " + t.getMessage());
}
try {
driver.get(base + "/login");
SessionCache.store("sso", driver);

driver.manage().deleteAllCookies();
driver.get("about:blank"); // where a fresh session sits before the test
SessionCache.restore("sso", driver);

driver.get(base + "/");
assertNotNull(driver.manage().getCookieNamed("sso"),
"cookie cached by the provider should be restored for the next test");
} finally {
driver.quit();
}
}
}
Loading