Skip to main content

Tips: Infrastructure, Network, and Visual

Deeper infrastructure, network, and cross-cutting quality tips for running SHAFT against real execution environments.

Native Selenium WebDriver access

Access the native Selenium WebDriver instance whenever SHAFT's fluent API doesn't cover a case:

NativeDriverAccess.java
SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver();
WebDriver nativeDriver = driver.getDriver();
nativeDriver.findElement(By.id("lname")).sendKeys("Smith");

You can also wrap any existing org.openqa.selenium.WebDriver instance with SHAFT capabilities — useful when migrating from plain Selenium incrementally, or when a third-party library hands you a raw driver:

WrapExistingWebDriver.java
WebDriver rawDriver = new ChromeDriver();
rawDriver.get("https://example.com");

// Wrap it — all SHAFT fluent actions, smart waits, and reporting now apply
SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(rawDriver);
driver.assertThat().browser().title().contains("Example");
tip

After wrapping, call driver.quit() (not rawDriver.quit()) so SHAFT's lifecycle hooks run and the Allure report is finalized.

Custom browser capabilities

Pass a MutableCapabilities or browser-specific options object directly to the SHAFT.GUI.WebDriver constructor to configure anything Selenium supports — arguments, experimental options, proxies, extensions:

CustomCapabilities.java
import org.openqa.selenium.chrome.ChromeOptions;

ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-extensions", "--disable-notifications", "--no-sandbox");

SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(DriverType.CHROME, options);

The same pattern applies to FirefoxOptions and EdgeOptions. Common use cases: a custom download directory (prefs.put("download.default_directory", ...)), an HTTP proxy (options.setProxy(proxy)), or loading a browser extension (options.addExtensions(new File(...))).

tip

Prefer SHAFT Properties for ordinary browser configuration; reach for custom capabilities only for browser-specific settings that aren't exposed as SHAFT properties. On Selenium Grid or cloud platforms, custom capabilities are merged with the platform-level capabilities from your properties file.

Mobile emulation

SHAFT supports Chrome DevTools mobile emulation to render a page as it would appear on a real device, without Appium or physical hardware — ideal for responsive-layout checks and quick mobile smoke tests:

MobileEmulation.java
SHAFT.Properties.web.set()
.isMobileEmulation(true)
.mobileEmulationIsCustomDevice(false)
.mobileEmulationDeviceName("iPhone 12 Pro");

SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver();

For a device outside Chrome's preset list, or to pin an exact viewport, use custom dimensions instead:

CustomDeviceEmulation.java
SHAFT.Properties.web.set()
.isMobileEmulation(true)
.mobileEmulationIsCustomDevice(true)
.mobileEmulationWidth(390)
.mobileEmulationHeight(844)
.mobileEmulationPixelRatio(3.0)
.mobileEmulationUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) ...");

:::warning Device-list drift The browser's built-in device list changes across releases (Chrome 143 removed "Pixel 5", for example), and an unknown device name fails session creation with the opaque entry 0 of 'firstMatch' is invalid. SHAFT pins "Pixel 5" — its long-standing default — to explicit device metrics so it keeps working on every Chromium browser. If a previously working device name starts failing, switch to custom device emulation with the device's width, height, pixel ratio, and user agent. :::

Local Selenium Grid execution

Running a local Grid is useful for distributed execution without cloud services, cross-browser/OS testing on one network, and debugging remote-execution issues before deploying to a managed Grid. The fastest path is Docker Compose:

docker-compose.yml
services:
selenium-hub:
image: selenium/hub:latest
ports: ["4444:4444", "4442:4442", "4443:4443"]
chrome:
image: selenium/node-chrome:latest
shm_size: 2gb
depends_on: [selenium-hub]
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
docker-compose up -d
# Console: http://localhost:4444
docker-compose up -d --scale chrome=3 # scale a node
docker-compose down

Without Docker, run the standalone Selenium Server JAR: java -jar selenium-server-<version>.jar hub, then java -jar selenium-server-<version>.jar node --hub http://localhost:4444 (or standalone for both in one process).

Point SHAFT at the Grid:

src/main/resources/properties/custom.properties
executionAddress=localhost:4444
targetOperatingSystem=Linux
targetBrowserName=chrome
headlessExecution=true

# Inspect Grid capacity before creating remote sessions (Selenium Grid 4)
remotePreflightEnabled=true
remoteAdaptiveSessionThrottling=true

For Selenium Grid 4, SHAFT can query /status and /graphql before remote session creation; the preflight summary is attached to the report. Use remoteAdaptiveSessionThrottling=true when local TestNG/JUnit parallelism can exceed Grid capacity, and remotePreflightFailFast=true when a missing browser slot should fail before WebDriver retries.

Kubernetes Selenium Grid

For a cloud-native, auto-scaling Grid, deploy on Kubernetes with KEDA (Kubernetes Event-Driven Autoscaler) — browser nodes scale from zero to capacity on demand and back down when tests finish:

helm repo add docker-selenium https://www.selenium.dev/docker-selenium
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

helm install keda kedacore/keda --namespace keda --create-namespace

helm install selenium-grid docker-selenium/selenium-grid \
--set autoscaling.enabled=true \
--set autoscaling.scaledOptions.minReplicaCount=0 \
--set autoscaling.scaledOptions.maxReplicaCount=8

Point SHAFT at the in-cluster Service DNS name (or port-forward for access from outside the cluster):

src/main/resources/properties/custom.properties
executionAddress=selenium-grid-selenium-hub.default.svc.cluster.local:4444
targetOperatingSystem=Linux
targetBrowserName=chrome
# From outside the cluster
kubectl port-forward svc/selenium-grid-selenium-hub 4444:4444

:::tip Best practices Keep minReplicaCount=0 to avoid paying for idle browser nodes; set Kubernetes resource requests/limits (browser containers are memory-intensive); use a dedicated namespace; store values.yaml in version control; use Kubernetes Secrets for Grid credentials. :::

Network mocking and interception

driver.browser().interceptRequest() matches outgoing browser requests to replace responses with controlled test data, or lets real responses continue and validates them with SHAFT API validations. It relies on Selenium DevTools, so use a DevTools-capable browser (Chrome or Edge).

NetworkMocking.java
driver.browser()
.interceptRequest()
.get()
.urlContains("/api/users")
.queryParam("role", "admin")
.respond()
.statusCode(200)
.jsonBody("{\"users\":[{\"name\":\"Mock User\"}]}");

driver.browser().navigateToURL("https://example.com/dashboard");

Use assertResponse(...) / verifyResponse(...) to validate a real response instead of mocking it:

NetworkValidation.java
driver.browser()
.interceptRequest()
.get()
.pathEquals("/api/users")
.assertResponse(response -> response.extractedJsonValue("users.size()").isEqualTo("1"));

routeFromHar() replays a previously captured HAR 1.2 file as mocked responses (works on both SHAFT.GUI.WebDriver and SHAFT.GUI.Playwright); a compatible HAR can come from a Capture session or any HAR 1.2 export. Contract recording/replay (startContractRecording, replayContract, assertContract) shares the same contract mode as SHAFT.API — see UI and API contract replay.

Network profiles simulate offline/slow/blocked conditions:

NetworkProfiles.java
driver.browser().throttleNetwork(300, 128, 64).and().navigateToURL("https://example.com/reports");
driver.browser().restoreNetwork();
driver.browser().blockNetworkResources("*.png", "*.webp").and().goOffline();
note

Interception rules are scoped to the current browser session and cleared automatically on quit(), or explicitly with driver.browser().clearNetworkInterceptors(). If multiple rules match the same request, the latest registered rule wins. When shaft.trace.includeNetwork=true, SHAFT also passively records network exchanges into the failure trace bundle (shaft-network.har) with secrets masked.

Using cookies and cached authentication

Move authentication state between an API session and a browser session:

CookieBridging.java
// API login -> browser session
SHAFT.API api = new SHAFT.API("https://api.example.com");
api.post("/auth/login").setRequestBody(credentials).setContentType(ContentType.JSON);

SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver();
driver.browser().navigateToURL("https://app.example.com").importCookiesFrom(api);

// Browser login -> API session (the inverse: exportCookiesTo(...))
api.importCookiesFrom(driver.browser());

SHAFT.API also accepts a storage-state JSON path directly in its constructor — the same file produced by driver.browser().saveStorageState(path):

SHAFT.API api = new SHAFT.API("https://api.example.com", "target/auth-state.json");

SHAFT.Auth.setup(name, flow) is a Playwright-style cached-authentication helper: it runs a login flow once per name and caches the resulting browser storage state to disk, so later calls — including from other tests, classes, or threads — reuse the cached session instead of repeating the UI login:

AuthSetup.java
// Once per suite, e.g. in a @BeforeSuite:
SHAFT.Auth.setup("standardUser", driver -> {
driver.browser().navigateToURL("https://example.com/login");
driver.element().type(By.id("username"), "standard_user");
driver.element().type(By.id("password"), "secret_sauce");
driver.element().click(By.id("login-button"));
});

// In every test, reuse the cached session:
SHAFT.Properties.web.set().storageStatePath(SHAFT.Auth.stateFile("standardUser"));
SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver();

The cache is valid as long as its file exists (no TTL) under SHAFT.Properties.paths.authCache() (default target/auth-cache/). Concurrent setup() calls for the same name are serialized on a per-name lock; calls for different names never block each other. See Auto-load storage state on driver init.

Accessibility testing

SHAFT integrates axe-core for automated WCAG audits directly within existing WebDriver tests — chain .accessibility() onto browser actions, no separate tooling required (add the com.deque.html.axe-core:selenium dependency):

AccessibilityTesting.java
driver.browser().navigateToURL("https://example.com")
.accessibility()
.assertNoCriticalViolations("Home Page");

driver.browser().navigateToURL("https://example.com")
.accessibility()
.assertAccessibilityScoreAtLeast("Home Page", 90.0);

driver.browser().navigateToURL("https://example.com")
.accessibility()
.analyzeWithIgnoredRules("Home Page", List.of("color-contrast", "image-alt"));
ScenarioMethod
Strict compliance gateassertNoCriticalViolations()
Progressive improvementassertAccessibilityScoreAtLeast()
Known accepted deviationsanalyzeWithIgnoredRules()
tip

Run accessibility audits in CI smoke tests to catch regressions early. Results (page name, score, violation details) are automatically included in the SHAFT Allure report.

Jira integration

SHAFT can automatically create Jira bugs and link test results to Jira issues. Create an Atlassian API token (Account Settings → Security → Create and manage API tokens), then configure JiraXRay.properties:

src/main/resources/properties/JiraXRay.properties
jiraInteraction=true
jiraUrl=https://your-jira-instance.atlassian.net
projectKey=PROJ
authorization=your-copied-token
reportTestCasesExecution=true
reportPath=target/surefire-reports/testng-results.xml
ExecutionName=RegressionTestExecution
ReportBugs=true
assignee=jira-user-id

Visual testing

Visual regression (matchesReferenceImage(), matchesScreenshot(), OpenCV/Applitools Eyes engines) has its own dedicated page — see Visual testing for the full engine comparison, the shaft-visual dependency decision, and per-browser/OS baseline naming.