blob: 8a761b83f6679afb911d768a85a35c73db393ff1 [file] [log] [blame] [edit]
// Copyright (C) 2025 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
#if ENABLE_SWIFTUI && canImport(Testing) && compiler(>=6.2)
import Testing
@_spi(Testing) import WebKit
private struct NeverLoadingSchemeHandler: URLSchemeHandler {
// This force unwrap is safe because the scheme is a static String.
// swift-format-ignore: NeverForceUnwrap
@MainActor
static let scheme = URLScheme("never-loading")!
nonisolated func reply(for request: URLRequest) -> some AsyncSequence<URLSchemeTaskResult, any Error> {
AsyncThrowingStream { _ in }
}
}
@MainActor
struct WebPageNavigationTests {
@Test
func basicNavigationProducesExpectedNavigationEvents() async throws {
let page = WebPage()
let html = "<html><div>Hello</div></html>"
let sequence = page.load(html: html)
let expected: [WebPage.NavigationEvent] = [.startedProvisionalNavigation, .committed, .finished]
let actual = try await Array(sequence)
#expect(actual == expected)
}
@Test
func failedNavigationProducesExpectedNavigationError() async throws {
let page = WebPage()
let sequence = page.load(URL(string: "about:foo"))
var actual: [WebPage.NavigationEvent] = []
let expected: [WebPage.NavigationEvent] = [.startedProvisionalNavigation]
await #expect(throws: (any Error).self) {
// Safety: this is actually safe; false positive is rdar://154775389
for try await unsafe event in sequence {
actual.append(event)
}
}
#expect(actual == expected)
}
@Test
func explicitlyStopLoadingProgrammaticNavigation() async throws {
var configuration = WebPage.Configuration()
configuration.urlSchemeHandlers[NeverLoadingSchemeHandler.scheme] = NeverLoadingSchemeHandler()
let page = WebPage(configuration: configuration)
let sequence = page.load(URL(string: "never-loading:///index.html"))
// FIXME: `#expect` should work here, but due to a Swift Testing issue causes the test to hang.
do {
// Safety: this is actually safe; false positive is rdar://154775389
for try await unsafe event in sequence where event == .startedProvisionalNavigation {
page.stopLoading()
}
Issue.record("Stopping page load should trigger an error and therefore the loop should never finish.")
} catch {
#expect(error is WebPage.NavigationError)
}
}
@Test
func stopLoadingProgrammaticNavigationViaTaskCancellation() async throws {
var configuration = WebPage.Configuration()
configuration.urlSchemeHandlers[NeverLoadingSchemeHandler.scheme] = NeverLoadingSchemeHandler()
let page = WebPage(configuration: configuration)
let allNavigations = page.navigations
let sequence = page.load(URL(string: "never-loading:///index.html"))
var task: Task<Void, any Error>? = nil
await withCheckedContinuation { continuation in
task = Task {
// Safety: this is actually safe; false positive is rdar://154775389
for try await unsafe event in sequence {
if event == .startedProvisionalNavigation {
continuation.resume()
} else {
Issue.record("No other event should occur since the load is indefinite.")
}
}
}
}
try #require(task).cancel()
let expectedEvents: [WebPage.NavigationEvent] = [.startedProvisionalNavigation]
var actualEvents: [WebPage.NavigationEvent] = []
// FIXME: `#expect` should work here, but due to a Swift Testing issue causes the test to hang.
do {
// Safety: this is actually safe; false positive is rdar://154775389
for try await unsafe event in allNavigations {
actualEvents.append(event)
}
Issue.record("The stream is indefinite and therefore should never reach here.")
} catch {
#expect(error is WebPage.NavigationError)
}
#expect(actualEvents == expectedEvents)
}
@Test
func failedNavigationWithWebContentProcessTerminated() async throws {
var configuration = WebPage.Configuration()
configuration.urlSchemeHandlers[NeverLoadingSchemeHandler.scheme] = NeverLoadingSchemeHandler()
let page = WebPage(configuration: configuration)
let sequence = page.load(URL(string: "never-loading:///index.html"))
// FIXME: `#expect` should work here, but a Swift Testing issue causes the test to hang.
do {
// Safety: this is actually safe; false positive is rdar://154775389
for try await unsafe event in sequence where event == .startedProvisionalNavigation {
page.terminateWebContentProcess()
}
Issue.record("Terminating the web content process should trigger an error and therefore the loop should never finish.")
} catch {
#expect(error is WebPage.NavigationError)
}
}
@Test
func navigationProceedsAfterDiscardingNavigationStream() async throws {
let page = WebPage()
let html = "<title>A title</title>"
page.load(html: html)
// A timeout is used since observing the navigation sequence itself alters the outcome of this test.
try await Task.sleep(for: .seconds(10))
#expect(page.title == "A title")
}
}
#endif // ENABLE_SWIFTUI && canImport(Testing) && compiler(>=6.2)