PRACTICAL GUIDE / Selenium corporate PAC proxy configuration
Why a PAC-backed Selenium session routes the wrong way
Configure and diagnose PAC routing in Selenium across local and Grid browsers, including URL reachability, rule order, DNS, and safe CI checks.
In this guide7 sections
- Follow the configuration from the test to the browser
- Prove the node can obtain the exact PAC file
- Test rule order with destinations that expose the route
- Distinguish PAC failure from proxy, DNS, and TLS failure
- Roll the configuration through CI without hiding drift
- Read a route incident from end to end
- Know when PAC is the wrong test setup
What you will learn
- Follow the configuration from the test to the browser
- Prove the node can obtain the exact PAC file
- Test rule order with destinations that expose the route
- Distinguish PAC failure from proxy, DNS, and TLS failure
The browser opens and the intranet home page loads, but every public script times out. The same account works in a manually launched browser, and Selenium reports the expected PAC URL in its capabilities. That only proves the configuration was requested, not that the remote browser fetched the file or chose the route you expected.
Follow the configuration from the test to the browser
PAC stands for Proxy Auto-Configuration. The file contains a JavaScript function named FindProxyForURL(url, host). For each relevant request, the browser evaluates that function and receives an ordered routing result such as a corporate proxy, a direct connection, or more than one fallback choice.
Selenium does not interpret your company’s PAC rules. The WebDriver client serializes a standard proxy capability when it asks the driver or Grid node to create a session. For PAC mode, the capability contains proxyType: pac and proxyAutoconfigUrl. The remote end then takes implementation-specific steps to configure the browser. That boundary explains why a correct Java object can coexist with a browser that cannot reach the PAC server.
Keep three machines distinct in your notes. The test client runs Java and sends WebDriver commands. The driver or Grid endpoint creates the session. The browser process makes application requests and uses the configured proxy. All three can run on one laptop, but in Grid they are often in different containers or hosts. Checking a PAC URL with curl on the test client says nothing about DNS or routing inside the browser node.
The Java binding exposes the required fields directly. Set the proxy mode and URL before session creation, then inspect the returned capability for diagnostic context. Do not treat the returned value as proof of a successful PAC download. It is evidence about session negotiation.
package example.proxy;
import java.net.URI;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
public final class PacDriverFactory {
private PacDriverFactory() {}
public static RemoteWebDriver create(String pacUrl) {
if (pacUrl == null || pacUrl.isBlank()) {
throw new IllegalArgumentException("PAC_URL must be set");
}
URI pacUri = URI.create(pacUrl);
String scheme = pacUri.getScheme();
if (pacUri.getHost() == null
|| !("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))) {
throw new IllegalArgumentException("PAC_URL must be an absolute HTTP(S) URL");
}
Proxy proxy = new Proxy();
proxy.setProxyType(Proxy.ProxyType.PAC);
proxy.setProxyAutoconfigUrl(pacUrl);
ChromeOptions options = new ChromeOptions();
options.setProxy(proxy);
ChromeDriver driver = new ChromeDriver(options);
Capabilities actual = driver.getCapabilities();
Object negotiatedProxy = actual.getCapability("proxy");
if (negotiatedProxy == null) {
driver.quit();
throw new IllegalStateException("New session did not return a proxy capability");
}
return driver;
}
}For Grid, replace the local ChromeDriver with RemoteWebDriver pointed at the Grid URL, but keep the same options. The important consequence is unchanged: the node hosting the selected browser needs access to the PAC URL and to any proxy host the PAC can return. A CI runner outside the node cannot validate those paths on its behalf.
Do not mix proxy modes as a defensive measure. Manual fields are defined for proxyType: manual; the autoconfiguration URL is defined for proxyType: pac. Adding an HTTP proxy, an SSL proxy, a bypass list, PAC, and autodetection to one object does not create a reliable chain of fallbacks. It creates an ambiguous request whose handling can vary at the remote end or be rejected. Pick the mode that reflects the company policy.
PAC is also different from autodetection. A known URL is explicit PAC configuration. Autodetection asks the environment to discover proxy settings in an implementation-specific way. If a corporate workstation receives settings through device policy, a clean CI container may not receive them. Copying “autodetect worked on my laptop” into Grid is not a reproducible configuration.
Treat the PAC URL as immutable for a session. Proxy configuration is negotiated when the session is created. If the test needs to compare two PAC files, start two drivers. Trying to mutate browser preferences halfway through a session makes the before-and-after traffic hard to attribute and is not expressed as a standard WebDriver command.
Prove the node can obtain the exact PAC file
The first useful check runs in the same container, virtual machine, or host namespace as the browser. Fetch the precise configured URL. Resolve the PAC host. Record the response’s final URL if redirects are permitted by your organization. Hash the body so the test report can identify which revision was received without publishing the file itself.
Authentication is a common divider between interactive and automated browsers. A developer may already have an operating-system session, VPN route, device certificate, or cached enterprise authentication. A fresh browser container has none of that. The WebDriver PAC capability contains a URL, not a general-purpose credential bundle for the PAC server. If fetching the file requires a corporate identity, provision that identity through the approved node image or network policy rather than embedding credentials in the URL.
Run a shell check inside the browser image as an image smoke test. This example fails on an HTTP error, verifies that the body contains the required function, and prints only a checksum. It does not print the PAC rules, which may reveal internal hostnames.
#!/usr/bin/env bash
set -euo pipefail
: "${PAC_URL:?PAC_URL must be set}"
pac_file="$(mktemp)"
trap 'rm -f "$pac_file"' EXIT
curl --fail --silent --show-error \
--connect-timeout 5 \
--max-time 15 \
--location \
--output "$pac_file" \
"$PAC_URL"
if ! grep -Eq 'function[[:space:]]+FindProxyForURL[[:space:]]*\(' "$pac_file"; then
echo "PAC response does not define FindProxyForURL" >&2
exit 1
fi
printf 'pac_sha256='
sha256sum "$pac_file" | awk '{print $1}'This check has a deliberate limit. curl proves that the process environment can fetch bytes using its own networking and trust configuration. It does not prove the browser accepted, cached, or evaluated them. That is why the next layer must navigate a route matrix through a real WebDriver session.
Check the content returned at the final URL, not merely a 200 response. A single sign-on gateway can return an HTML login page with status 200. A reverse proxy can return a corporate error page. Both look healthy to a shallow probe and fail as PAC scripts. Looking for FindProxyForURL catches the most obvious substitution; a checksum or signed version endpoint lets the network team identify the deployed rules.
Make the PAC file available before launching the browser. Some teams start a local file server in test setup and immediately create a session. On a loaded runner, the browser can request the URL before the server is listening. Bind the server to an address reachable from the browser container, wait for its health check, then create the driver. localhost refers to the environment where it is resolved, so a PAC URL using http://localhost will not reach a server running in a different container.
Network names need the same precision. A short host such as proxy01 may resolve on a managed workstation because it has a corporate search domain. The Grid container may need the fully qualified name. A PAC rule can also call DNS-related helpers, so the node’s resolver affects the branch chosen. Record /etc/resolv.conf or the platform-equivalent resolver configuration as environment evidence when local and Grid choices differ, but do not publish internal details in public artifacts.
PAC caching creates another diagnostic trap. An engineer edits the file, refreshes the product page, and assumes the new rules ran. Browsers and intermediaries can retain configuration. Start a fresh browser session and identify the served file by an approved version or hash before comparing behavior. Appending random query parameters may defeat an intermediary cache, but it may also conflict with corporate allowlists or signed URLs, so do not make that the default fix.
Test rule order with destinations that expose the route
A PAC file is executable routing policy. Review it like code. The first matching return ends evaluation, and each returned string can contain an ordered list of routes. A broad direct rule near the top can shadow a proxy rule below it. A permissive DIRECT fallback can keep tests green during a proxy outage while silently bypassing inspection.
Keep the file deterministic where possible. Host and suffix comparisons are cheaper to reason about than time-dependent rules or repeated DNS lookups. MDN documents helpers including isPlainHostName, dnsDomainIs, isResolvable, isInNet, and shExpMatch. DNS-based helpers can behave differently across node networks and add resolver work. Use them only when the routing requirement genuinely depends on resolution.
The following PAC function is a complete example for an imaginary company. It sends plain internal names and the corporate domain directly, sends a test-only forbidden domain to a controlled denial proxy, and sends other hosts to two egress proxies without a direct fallback.
function FindProxyForURL(url, host) {
const normalizedHost = host.toLowerCase();
if (
isPlainHostName(normalizedHost) ||
normalizedHost === "corp.example" ||
dnsDomainIs(normalizedHost, ".corp.example")
) {
return "DIRECT";
}
if (
normalizedHost === "blocked.test" ||
dnsDomainIs(normalizedHost, ".blocked.test")
) {
return "PROXY deny-proxy.corp.example:8080";
}
return "PROXY proxy-a.corp.example:8080; PROXY proxy-b.corp.example:8080";
}A PAC file is plain JavaScript served with the proxy auto-config content type, so the body above is the whole file. A production PAC file should still be validated with the tools and syntax supported by the browsers in scope. Do not assume every modern JavaScript feature is available in every PAC runtime.
The example assumes deny-proxy.corp.example is an owned service that records and rejects the request according to policy. A made-up dead port is not a dependable deny control: a listener can appear later, and a generic connection failure cannot prove that policy handled the destination.
Rule order matters in a concrete way. If dnsDomainIs(host, ".example") appears before dnsDomainIs(host, ".blocked.test.example"), the broader rule wins. If isPlainHostName(host) returns direct before an internal short name that should use a proxy, the special case is unreachable. Reviewers should build a small input matrix and state the expected route for each row. That gives changes a meaningful diff.
HTTPS adds a subtle limitation. PAC engines commonly strip path and query information from the url argument for HTTPS targets to avoid leaking sensitive data to the PAC script. A rule that tries to proxy https://vendor.example/admin but send https://vendor.example/public directly cannot rely on seeing those paths consistently. Prefer host-based policy, or enforce path access at an HTTP-aware layer designed for it.
A browser smoke test should choose destinations whose server response identifies success without exposing the proxy. Use organization-owned endpoints with stable markers: one direct intranet endpoint, one external endpoint that is reachable only through the corporate proxy, one proxy health target, and one destination expected to fail. Do not use a popular public website as the oracle. Its availability, bot controls, redirects, and regional responses are outside your contract.
This Java test accepts the successful route endpoints from the environment and asserts unique markers. Each assertion can fail if traffic takes an unusable path or reaches the wrong service. It deliberately avoids claiming which hop was used; proxy access logs or a dedicated response header should provide that evidence when available. Keep the forbidden destination in a separate check backed by proxy or destination records, because “the page did not load” can also pass during an unrelated outage.
Read every environment variable through requiredEnv rather than System.getenv directly. Map.of rejects a null key with a bare NullPointerException, so an unset PROXY_CHECK_URL would abort the test with a stack trace that names neither the variable nor the reason. The named check reports which variable is missing before the browser starts. The three URLs must also be distinct, because Map.of throws on a duplicate key.
package example.proxy;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
final class PacRouteSmokeTest {
private static WebDriver driver;
private static WebDriverWait wait;
@BeforeAll
static void startBrowser() {
driver = PacDriverFactory.create(requiredEnv("PAC_URL"));
wait = new WebDriverWait(driver, Duration.ofSeconds(20));
}
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must be set");
}
return value;
}
@AfterAll
static void stopBrowser() {
if (driver != null) {
driver.quit();
}
}
private static void assertMarker(String url, String expectedMarker) {
driver.get(url);
String marker = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("route-marker"))
).getText();
assertTrue(
marker.equals(expectedMarker),
() -> "Expected marker " + expectedMarker + " at " + url + " but got " + marker
);
}
@Test
void required_routes_reach_their_owned_endpoints() {
Map.of(
requiredEnv("DIRECT_CHECK_URL"), "direct-ok",
requiredEnv("PROXY_CHECK_URL"), "proxy-ok",
requiredEnv("INTERNAL_CHECK_URL"), "internal-ok"
).forEach(PacRouteSmokeTest::assertMarker);
}
}Keep the matrix small. Its purpose is to catch a broken route before the full suite produces hundreds of navigation failures. Product tests still cover their pages, but a failing route smoke test gives the incident an infrastructure-shaped starting point.
Distinguish PAC failure from proxy, DNS, and TLS failure
Four failures can show the same white browser page. First, the PAC file was not obtained. Second, the file ran and returned an unusable proxy. Third, the chosen direct path could not resolve or reach the destination. Fourth, the route worked but TLS validation rejected the certificate. Increasing Selenium’s page-load timeout treats all four as slowness and preserves none of the evidence needed to separate them.
For a PAC fetch failure, look for absence of the expected PAC version at the node, a request failure in browser network diagnostics, or no PAC-server access record for that node. Compare the exact URL, including scheme, host, port, and path. A capability echo proves only that the value crossed session creation.
For a proxy connection failure, the PAC server may show a successful file fetch while the corporate proxy has no request from the browser node. Resolve and connect to every proxy host that the relevant rule can return. If the first proxy is down and the result lists a second proxy, confirm the second receives the attempt. If the result ends in DIRECT, decide whether direct fallback is allowed. Do not celebrate a green test until you know which route succeeded.
Proxy authentication produces its own evidence. The proxy receives the request and answers with an authentication challenge or denial. That is not a bad PAC branch. It means the branch selected the proxy but the browser session lacks an accepted identity. Coordinate the browser image, enterprise policy, and proxy team. Hard-coding a username and password into the PAC file or repository turns a routing fix into a credential leak.
DNS failures depend on the chosen route. A direct connection relies on the node’s ability to resolve and reach the destination. A proxied connection may delegate destination resolution differently, depending on the proxy and protocol. Do not infer the branch from a hostname lookup on the Java test client. Correlate the PAC rule, node resolver, and proxy request.
TLS interception is another near-miss. A corporate proxy can be reachable and route the request correctly while the browser rejects the certificate chain presented for the target. Install the approved corporate trust anchor in the browser image if that reflects the managed production environment. acceptInsecureCerts can be useful in a deliberately untrusted test environment, but it changes the session’s certificate behavior and should not become a blanket proxy fix.
Application failure comes last. If the route endpoint receives the browser request and responds, the PAC path may be healthy even when the page assertion fails. Save the HTTP status and an approved response identifier through server-side logs. A missing element after a valid response belongs to the application or test. A navigation failure before any server request belongs earlier in the chain.
Correlation beats a giant browser dump. Give each smoke run a test ID in an ordinary request header only if your stack supports adding one without changing the route, or encode a non-secret ID in a dedicated diagnostic URL. Search PAC server, proxy, destination, and Grid records for the same time and node. Be careful with full URLs because query strings can contain secrets.
Roll the configuration through CI without hiding drift
Store the PAC URL as environment configuration, not a literal scattered through tests. Keep one driver factory responsible for translating it into a WebDriver capability. Log the URL’s origin and a safe file version, but redact credentials and sensitive query values. Reject an empty or malformed URL before asking Grid for a session.
Build the reachability probe into the browser image pipeline. Then run the browser route matrix at the beginning of the test job. If either fails, stop before product tests. This turns a wall of Selenium failures into “PAC file unavailable on node image” or “external proxy route did not reach its marker.”
name: selenium-pac-smoke
on:
pull_request:
paths:
- "src/test/**"
- "pom.xml"
schedule:
- cron: "17 * * * *"
jobs:
route-matrix:
runs-on: [self-hosted, corporate-network]
timeout-minutes: 15
env:
PAC_URL: ${{ vars.SELENIUM_PAC_URL }}
DIRECT_CHECK_URL: ${{ vars.DIRECT_CHECK_URL }}
PROXY_CHECK_URL: ${{ vars.PROXY_CHECK_URL }}
INTERNAL_CHECK_URL: ${{ vars.INTERNAL_CHECK_URL }}
steps:
- uses: actions/checkout@v4
- name: Verify PAC response from the browser runner
run: bash ci/check-pac.sh
- name: Run the owned route matrix
run: ./mvnw -B -Dtest=PacRouteSmokeTest testIf browsers run in a separate Grid, the first step belongs in the node image or a command executed in the node environment. Leaving it on the Actions runner would test the wrong network. The browser test still exercises the Grid node because the WebDriver session runs there.
Roll out a PAC change with a canary node pool. Record the old and new PAC checksums, run the same route matrix against both, and compare explicit outcomes. Promote the new pool only after direct, proxy, internal, and denied cases match the approved policy. Keep an easy way to select the old pool while investigating, but do not silently fall back without reporting which pool handled the session.
Version the test matrix with the policy. When a domain moves from direct to proxy, update the expected route evidence and endpoint ownership in the same reviewed change. A marker that merely says “page loaded” will not detect an unauthorized direct fallback. Where security requires proxy traversal, use proxy-side evidence or an endpoint reachable only through that controlled route.
The added checks cost startup time and infrastructure coordination. A fresh browser plus three navigations can add noticeable latency to a small suite. PAC hashing and node probes add maintenance. Pay that cost once per job or node image, not once per product test. The savings appear when a routing outage produces one precise failure instead of a wall of unrelated element timeouts.
Read a route incident from end to end
Take a failure where the direct intranet marker passes and the external marker times out. The returned capability contains the expected PAC URL. The node-side probe fetched the approved checksum. The PAC server and proxy both saw requests from the node, but the destination saw none. That sequence clears Selenium capability negotiation and PAC delivery. It points to the proxy's upstream route, policy, or name resolution. Rebuilding locators cannot affect it.
Change one fact and the owner changes. The proxy has no request, although the node fetched the PAC. A standalone evaluator says the external hostname should select PROXY, but the browser reached the destination directly. Check the actual host passed to FindProxyForURL, rule order, cached PAC revision, and any direct fallback. The standalone evaluator is useful only if it ran the same file and input semantics as the browser. An HTTPS path-based condition is especially suspect because the PAC runtime may not receive that path.
A third incident begins before either route. The node probe receives an HTML sign-in page from the PAC URL with status 200. The browser capability still echoes the configured URL, and application navigation fails. The correct fix is to make PAC delivery available to the node's approved machine identity or move the file to a suitable authenticated distribution path. Adding proxy credentials, accepting insecure certificates, or lengthening page load cannot turn an HTML page into FindProxyForURL.
Denied destinations need equally careful evidence. If a forbidden host unexpectedly loads, do not assert that the PAC was bypassed until you identify the route. The browser may have used a DIRECT fallback, a proxy may have allowed the request contrary to policy, or the hostname may have missed a suffix rule. A proxy access record, destination record, and PAC version separate those causes. A page-load assertion alone proves the control failed, which is enough to block, but not enough to assign the repair.
Roll these facts into the incident template: browser node image, returned browser version, PAC URL identifier, PAC checksum, tested hostname, expected route class, observed destination outcome, and the presence or absence of PAC-server, proxy, and destination records. Leave credentials, complete internal rules, and sensitive query strings out. That compact record lets the network team reproduce the route without receiving an entire Selenium artifact bundle.
During recovery, rerun the four-case matrix before the product suite. A proxy fix can restore the external path while accidentally changing an internal bypass. A PAC edit can repair one suffix and shadow the deny rule below it. The matrix is small enough to test every required class after each change, and each result can fail independently.
Know when PAC is the wrong test setup
Do not introduce a PAC file when every test destination uses one fixed proxy. Manual proxy configuration is simpler and has fewer moving parts. PAC earns its place when routing genuinely varies by host, network, or another supported condition.
Avoid copying a workstation PAC URL into a cloud Grid that has no corporate network path. Either place appropriately secured nodes on the required network, provide a supported proxy service reachable from the cloud environment, or run those tests elsewhere. Browser flags cannot create a missing route.
Do not add DIRECT as a final fallback merely to make CI resilient. In a security-controlled network, direct egress may violate inspection, data-loss prevention, or allowlisting rules. If direct fallback is approved, test and report it explicitly. If it is forbidden, let the smoke test fail when both proxies are unavailable.
Skip URL-path routing for HTTPS targets. The PAC function may not receive the path or query, so a rule that appears correct in a standalone JavaScript test can choose differently in the browser. Split traffic by host or move path-aware decisions to a component that actually sees the HTTP request.
Do not use Selenium to unit-test hundreds of PAC branches. A PAC-specific evaluator can exercise the rule matrix faster, including boundary hostnames and order changes. Keep a few WebDriver smoke cases to prove browser integration and node reachability. The two layers catch different defects.
Finally, do not call a capability assertion a routing test. Seeing proxyType and proxyAutoconfigUrl in returned capabilities is useful configuration evidence. It cannot fail when a reachable PAC file contains the wrong rule, when a proxy host is down, or when the node uses the wrong DNS. Only a destination outcome paired with route evidence can support that claim.
// FIELD DISPATCH
Get the QA Field Notes
Weekly QA battles, AI testing guides, and interview drills. Free on Substack.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 02Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 03Official w3.org reference
w3.org
Primary documentation selected and verified for the claims in this guide.
- 04Official developer.mozilla.org reference
developer.mozilla.org
Primary documentation selected and verified for the claims in this guide.
FAQ / QUICK ANSWERS
Questions testers ask
How do I set a PAC URL in Selenium Java?
Create an `org.openqa.selenium.Proxy`, set its type to `PAC`, set `proxyAutoconfigUrl`, and pass it through the browser options before creating the session. The URL is a session capability, so recreate the driver when it changes.
Why does the PAC file work on my laptop but not Selenium Grid?
The browser runs on the Grid node, and that environment must resolve and fetch the PAC URL. Test the URL, its DNS, and any required network route from the node rather than from the machine that launches the test.
Can I combine manual proxy fields with a PAC configuration?
Choose one proxy mode for the session. The WebDriver specification associates `httpProxy`, `sslProxy`, and `noProxy` with manual mode, while `proxyAutoconfigUrl` belongs to PAC mode.
Does setting acceptInsecureCerts fix a broken corporate proxy?
No, it changes certificate handling for the session and does not make an unreachable PAC URL or proxy host reachable. Use it only when accepting the test environment's certificates is an intentional part of that test.
How should CI prove that important PAC routes still work?
Run a small route matrix from the same browser image as the suite: one direct host, one proxied host, one internal host, and one forbidden destination. Pair the browser result with PAC version and proxy-side evidence.
RELATED GUIDES
Continue the learning route
GUIDE 01
Debug Selenium Manager Proxy and Cache Failures
Master debug Selenium manager proxy cache with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 02
Migrate Selenium 3 Capabilities and Grid Configuration to Selenium 4
Migrate Selenium 3 suites to Selenium 4 with W3C capability names, typed browser options, side-by-side Grid rollout, compatibility checks, and rollback.
GUIDE 03
A Production TOML Baseline for Selenium Grid 4
Build a production Selenium Grid TOML baseline with explicit topology, registration trust, Router authentication, node stereotypes, logs, and health checks.
GUIDE 04
Selenium Grid Trace Correlation with Test IDs
Master Selenium grid trace correlation with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 05
Selenium Java Tutorial: Build a Maintainable Test Suite
Selenium Java tutorial for beginners covering Maven setup, WebDriver, waits, TestNG, JUnit, page objects, debugging, CI, and reliable UI tests.