Test persistent and ephemeral WKWebView session boundaries

Test persistent and ephemeral WKWebView session boundaries

When a team runs iOS regression tests on a cloud Mac, its app may open two web sessions in sequence: first signing in to workspace A, then switching to workspace B. If B can read A’s cookie, the interface may still appear to load normally. The problem might surface only when a user switches identities. Rather than checking only that “the page opens,” turn the website data store boundary into a repeatable test.

Define what needs to be isolated

WKWebView selects its website data store through WKWebViewConfiguration.websiteDataStore. WKWebsiteDataStore.default() is for persistent data; each call to WKWebsiteDataStore.nonPersistent() can create a separate ephemeral store instance. The first goal here is to verify cookie isolation between two independent ephemeral stores, not to treat that result as proof that the entire sign-in flow is secure.

Before writing the test, find where the app creates its web views and record which store is bound to each view. If the product requires multiple pages for the same identity to share a session, those pages should not mistakenly use separate ephemeral stores. If switching identities should start a new session, confirm that the new view does not keep referencing the old store. A table specifying which pages share a store and which do not is more reliable than trying to infer a cookie’s origin afterward.

WKProcessPool is not the acceptance criterion here. Do not conclude that website data is shared or isolated solely because process pools are the same or different; check the websiteDataStore actually bound to each view.

Write a store test that needs no external network

Add WebKit and XCTest to the app’s unit test target. The following test interacts only with cookie stores and makes no network requests, so it needs neither a test server nor predictable page-load times. test.invalid is the sample cookie’s domain, not a service address.

import WebKit
import XCTest

final class WebsiteDataIsolationTests: XCTestCase {
    func testEphemeralStoresDoNotShareCookie() {
        let storeA = WKWebsiteDataStore.nonPersistent()
        let storeB = WKWebsiteDataStore.nonPersistent()
        let done = expectation(description: "Both stores checked")

        guard let cookie = HTTPCookie(properties: [
            .domain: "test.invalid",
            .path: "/",
            .name: "isolation_probe",
            .value: "only_a"
        ]) else {
            XCTFail("Could not create probe cookie")
            return
        }

        storeA.httpCookieStore.setCookie(cookie) {
            storeA.httpCookieStore.getAllCookies { cookiesA in
                XCTAssertTrue(cookiesA.contains {
                    $0.name == "isolation_probe" && $0.value == "only_a"
                })

                storeB.httpCookieStore.getAllCookies { cookiesB in
                    XCTAssertFalse(cookiesB.contains {
                        $0.name == "isolation_probe"
                    })
                    done.fulfill()
                }
            }
        }

        wait(for: [done], timeout: 5)
    }
}

First confirm that A can read the value just written; then confirm that B cannot read a cookie with the same name. The order matters: reading B before the write completes could produce a “passing” test even if the stores incorrectly share data. A timeout means the callback chain did not finish. Investigate it as a failure rather than increasing the timeout indefinitely.

Connect the store to real pages

After the store-level test passes, check how the app creates its views. Set the configuration before initializing WKWebView; changing a different configuration afterward will not replace the store on an existing view. In a page test’s setup code, pass the intended instance explicitly:

let sessionStore = WKWebsiteDataStore.nonPersistent()
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = sessionStore
let webView = WKWebView(frame: .zero, configuration: configuration)

The subsequent integration test should load a controlled test page, have it sign in, read its state, switch identities, and sign out, then inspect the requests and what the page displays. The store API can show that a test cookie is not visible across the two instances. On its own, it cannot prove that the server-side session has been invalidated or that the app has not reinjected a token into the page from native code. If the test needs real requests, use a controlled service maintained by the team and record network failures separately from session assertions.

Test persistent stores separately

Do not infer that sign-out works correctly from a passing nonPersistent() test. If the app uses default(), a sign-out test should check whether the relevant website data in that persistent store is handled according to product rules, then recreate the web view and test again. Limit the scope of data to be removed before clearing it. Do not indiscriminately delete records that other pages on the device may still need just to make a test pass.

Keep results reproducible on a cloud Mac

Run the same test target on the development desktop and in automated jobs, retaining the results from each. Before running it, pin the Xcode version, simulator OS version, and test configuration required by the project; there is no need to assume a particular OS version will always be suitable. Create fresh ephemeral stores in each test method and use a dedicated name for the test cookie so test cases do not borrow one another’s state.

If a test passes on its own but fails in the full suite, check three things first: whether the test has inadvertently switched to default(); whether the two web views reuse the same store instance; and whether asynchronous writes, page navigation, and assertions are ordered by completion callbacks. Do not start by increasing wait times to hide the problem. For later investigation, save the failing test case name, the Xcode and simulator versions used, and the test results file. Do not log real session values.

Pre-delivery checklist

Check Passing criterion
Store ownership Each web view’s websiteDataStore matches its intended session
Ephemeral isolation The test cookie written to A is not visible in independent store B
Callback order Read A after the write completes, then read B
Sign-out verification Check persistent data and the server-side session separately against product rules
Failure evidence Record the environment and assertions, not real credentials

This test works best as a boundary test: first establish that local data does not flow between ephemeral stores, then use page-level cases to verify identity switching and sign-out. Record the two conclusions separately so a failure points you toward store bindings, asynchronous ordering, or the app’s own session logic.

Frequently asked questions

Does an ephemeral data store clear persistent cookies?

No. nonPersistent() does not clean default(). Test logout against the persistent store your app actually uses.

Are separate WKProcessPool instances enough to isolate cookies?

Do not assume so. Check the WKWebsiteDataStore assigned to each web view and test page requests when that behavior matters.

XCZone · Cloud Mac

Need a dedicated physical Mac you can access remotely?

Compare the specs, locations, and billing cycles for M4 Start, M4 Work, and M4 Pro, then choose the plan that fits your workload.

Explore Cloud Mac Plans