Tests/LibWeb: Cover UI-owned session history

Teach test-web to expose the UI-process history dump. Add focused
navigation tests for same-document traversal, fallback traversal, and
cross-document browser back and forward behavior. The expectations
assert document state and the UI-owned history snapshot.
This commit is contained in:
Andreas Kling 2026-06-13 16:06:58 +02:00 committed by Andreas Kling
parent 24f37c6732
commit 0aaae1ac76
19 changed files with 440 additions and 37 deletions

View file

@ -687,7 +687,44 @@ String Internals::dump_session_history()
String Internals::dump_ui_process_session_history()
{
return window().associated_document().page().client().page_did_request_ui_process_session_history_for_testing();
auto& document = window().associated_document();
if (auto navigable = document.navigable()) {
if (auto traversable = navigable->traversable_navigable();
traversable && document.page().client().should_report_session_history_updates()) {
auto session_history_snapshot = traversable->create_session_history_snapshot();
document.page().client().page_did_update_session_history(
session_history_snapshot.top_level_session_history_entries,
session_history_snapshot.used_session_history_steps,
session_history_snapshot.current_used_step_index);
}
}
return document.page().client().page_did_request_ui_process_session_history_for_testing();
}
GC::Ref<WebIDL::Promise> Internals::flush_session_history_traversal_queue()
{
auto& realm = this->realm();
auto promise = WebIDL::create_promise(realm);
auto& document = window().associated_document();
auto navigable = document.navigable();
if (!navigable) {
WebIDL::resolve_promise(realm, promise);
return promise;
}
auto traversable = navigable->traversable_navigable();
if (!traversable) {
WebIDL::resolve_promise(realm, promise);
return promise;
}
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [&realm, promise](NonnullRefPtr<Core::Promise<Empty>> signal) {
HTML::TemporaryExecutionContext execution_context { realm };
WebIDL::resolve_promise(realm, promise);
signal->resolve({});
}));
return promise;
}
GC::Ptr<DOM::ShadowRoot> Internals::get_shadow_root(GC::Ref<DOM::Element> element)

View file

@ -112,6 +112,7 @@ public:
String dump_gc_graph();
String dump_session_history();
String dump_ui_process_session_history();
GC::Ref<WebIDL::Promise> flush_session_history_traversal_queue();
GC::Ptr<DOM::ShadowRoot> get_shadow_root(GC::Ref<DOM::Element>);

View file

@ -98,6 +98,7 @@ interface Internals {
DOMString dumpGCGraph();
DOMString dumpSessionHistory();
DOMString dumpUIProcessSessionHistory();
Promise<undefined> flushSessionHistoryTraversalQueue();
// Returns the shadow root of the element, if it has one, even if it's not normally accessible to JS.
ShadowRoot? getShadowRoot(Element element);

View file

@ -14,6 +14,7 @@ set(TEST_SOURCES
TestMicrosyntax.cpp
TestMimeSniff.cpp
TestNumbers.cpp
TestPage.cpp
TestRefCountedTreeNode.cpp
TestSecureContexts.cpp
TestSourceHighlighter.cpp
@ -30,6 +31,7 @@ target_link_libraries(TestContentBlocker PRIVATE LibURL)
target_link_libraries(TestControlMessageQueue PRIVATE LibSync)
target_link_libraries(TestFetchResponse PRIVATE LibGC LibHTTP LibJS)
target_link_libraries(TestFetchURL PRIVATE LibURL)
target_link_libraries(TestPage PRIVATE LibGC LibJS)
target_link_libraries(TestSecureContexts PRIVATE LibURL)
target_link_libraries(TestSourceHighlighter PRIVATE LibURL LibWebView)

72
Tests/LibWeb/TestPage.cpp Normal file
View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/VM.h>
#include <LibTest/TestCase.h>
#include <LibWeb/Page/Page.h>
class TestPageClient final : public Web::PageClient {
GC_CELL(TestPageClient, Web::PageClient);
GC_DECLARE_ALLOCATOR(TestPageClient);
public:
virtual u64 id() const override { return 1; }
virtual Web::Page& page() override { return *m_page; }
virtual Web::Page const& page() const override { return *m_page; }
virtual bool is_connection_open() const override { return true; }
virtual Gfx::Palette palette() const override { VERIFY_NOT_REACHED(); }
virtual Web::DevicePixelRect screen_rect() const override { return {}; }
virtual double zoom_level() const override { return 1; }
virtual double device_pixel_ratio() const override { return 1; }
virtual double device_pixels_per_css_pixel() const override { return 1; }
virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override { return Web::CSS::PreferredColorScheme::Auto; }
virtual Web::CSS::PreferredContrast preferred_contrast() const override { return Web::CSS::PreferredContrast::Auto; }
virtual Web::CSS::PreferredMotion preferred_motion() const override { return Web::CSS::PreferredMotion::NoPreference; }
virtual size_t screen_count() const override { return 1; }
virtual Queue<Web::QueuedInputEvent>& input_event_queue() override { VERIFY_NOT_REACHED(); }
virtual void report_finished_handling_input_event(u64, Web::EventResult) override { }
virtual void request_frame() override { }
virtual void request_file(Web::FileRequest) override { }
virtual Web::DisplayListPlayerType display_list_player_type() const override { return Web::DisplayListPlayerType::SkiaCPU; }
virtual bool is_headless() const override { return true; }
virtual void visit_edges(Visitor& visitor) override
{
Base::visit_edges(visitor);
visitor.visit(m_page);
}
virtual bool page_did_request_traverse_the_history_by_delta(int delta, Web::HistoryTraversalPrecheck history_traversal_precheck) override
{
++traversal_request_count;
last_traversal_delta = delta;
last_history_traversal_precheck = history_traversal_precheck;
return accept_traversal_request;
}
bool accept_traversal_request { true };
size_t traversal_request_count { 0 };
Optional<int> last_traversal_delta;
Optional<Web::HistoryTraversalPrecheck> last_history_traversal_precheck;
GC::Ptr<Web::Page> m_page;
};
GC_DEFINE_ALLOCATOR(TestPageClient);
TEST_CASE(browser_traversal_requests_embedder)
{
auto vm = JS::VM::create();
auto client = vm->heap().allocate<TestPageClient>();
auto page = Web::Page::create(*vm, client);
client->m_page = page.ptr();
page->traverse_the_history_by_delta(-1);
EXPECT_EQ(client->traversal_request_count, 1uz);
VERIFY(client->last_traversal_delta.has_value());
EXPECT_EQ(*client->last_traversal_delta, -1);
VERIFY(client->last_history_traversal_precheck.has_value());
EXPECT_EQ(*client->last_history_traversal_precheck, Web::HistoryTraversalPrecheck::Needed);
}

View file

@ -0,0 +1,3 @@
location=?second
ui-current=?second
ui-buttons=true,false

View file

@ -0,0 +1,5 @@
after-navigation: location=?second
after-navigation: ui-current=?second
after-navigation: ui-current-entry=?second*
after-navigation: ui-webcontent-known-current-entry=?second*
after-navigation: ui-forward-button=false

View file

@ -0,0 +1,6 @@
after-back: location=?same-document-back-replaced
after-back: ui-current=?same-document-back-replaced
after-back: ui-relevant-entries=?same-document-back-replaced*,?same-document-back-pushed
after-back: ui-webcontent-known-relevant-entries=?same-document-back-replaced*,?same-document-back-pushed
after-back: ui-forward-button=true
after-back: ui-uses-ui-step-coordinates=true

View file

@ -0,0 +1,6 @@
after-push-state: location=?same-document-pushed
after-push-state: ui-current=?same-document-pushed
after-push-state: ui-relevant-entries=?same-document-replaced,?same-document-pushed*
after-push-state: ui-webcontent-known-relevant-entries=?same-document-replaced,?same-document-pushed*
after-push-state: ui-buttons=true,false
after-push-state: ui-uses-ui-step-coordinates=true

View file

@ -0,0 +1,63 @@
<!doctype html>
<script src="../include.js"></script>
<script>
function queryFromURL(url) {
return new URL(url).search || "(none)";
}
function currentUIHistory() {
return JSON.parse(internals.dumpUIProcessSessionHistory());
}
asyncTest(async done => {
history.replaceState({}, "", "?first");
history.pushState({}, "", "?second");
await internals.flushSessionHistoryTraversalQueue();
const initialUIHistory = currentUIHistory();
if (location.search !== "?second"
|| queryFromURL(initialUIHistory.currentURL) !== "?second"
|| !initialUIHistory.backButtonEnabled
|| initialUIHistory.forwardButtonEnabled)
println(`FAIL: unexpected initial UI history ${JSON.stringify(initialUIHistory)}`);
let didTraverse = false;
window.addEventListener("popstate", () => {
didTraverse = true;
});
await new Promise(resolve => {
window.addEventListener("message", event => {
if (event.data === "sandboxed-history-back-attempted")
resolve();
}, { once: true });
const iframe = document.createElement("iframe");
iframe.setAttribute("sandbox", "allow-scripts");
iframe.src = `data:text/html,${encodeURIComponent(`
<script>
history.back();
parent.postMessage("sandboxed-history-back-attempted", "*");
<\/script>
`)}`;
document.body.append(iframe);
});
if (didTraverse || location.search !== "?second") {
println(`unexpected traversal: location=${queryFromURL(location.href)}`);
done();
return;
}
const uiHistory = currentUIHistory();
if (queryFromURL(uiHistory.currentURL) !== "?second"
|| !uiHistory.backButtonEnabled
|| uiHistory.forwardButtonEnabled)
println(`FAIL: unexpected UI history ${JSON.stringify(uiHistory)}`);
println(`location=${queryFromURL(location.href)}`);
println(`ui-current=${queryFromURL(uiHistory.currentURL)}`);
println(`ui-buttons=${uiHistory.backButtonEnabled},${uiHistory.forwardButtonEnabled}`);
done();
});
</script>

View file

@ -0,0 +1,60 @@
<!doctype html>
<script src="../include.js"></script>
<script>
const testKey = "ui-process-session-history-dump-phase";
function queryFromURL(url) {
return new URL(url).search || "(none)";
}
function secondEntryQueries(entries) {
return entries
.filter(entry => queryFromURL(entry.url) === "?second")
.map(entry => `${queryFromURL(entry.url)}${entry.current ? "*" : ""}`)
.join(",");
}
function currentUIHistory() {
return JSON.parse(internals.dumpUIProcessSessionHistory());
}
function nextTask() {
return new Promise(resolve => {
const message = `ui-process-session-history-dump-${++nextTask.messageId}`;
addEventListener("message", event => {
if (event.source === window && event.data === message)
resolve();
}, { once: true });
postMessage(message, "*");
});
}
nextTask.messageId = 0;
function dump(label, uiHistory) {
println(`${label}: location=${queryFromURL(location.href)}`);
println(`${label}: ui-current=${queryFromURL(uiHistory.currentURL)}`);
println(`${label}: ui-current-entry=${secondEntryQueries(uiHistory.entries)}`);
println(`${label}: ui-webcontent-known-current-entry=${secondEntryQueries(uiHistory.webContentKnownEntries)}`);
println(`${label}: ui-forward-button=${uiHistory.forwardButtonEnabled}`);
}
asyncTest(async done => {
if (location.search === "?second") {
await nextTask();
const uiHistory = currentUIHistory();
if (queryFromURL(uiHistory.currentURL) !== "?second"
|| secondEntryQueries(uiHistory.entries) !== "?second*"
|| secondEntryQueries(uiHistory.webContentKnownEntries) !== "?second*"
|| uiHistory.forwardButtonEnabled
|| uiHistory.webContentKnownEntries.length !== 1)
println(`FAIL: unexpected UI history ${JSON.stringify(uiHistory)}`);
dump("after-navigation", uiHistory);
sessionStorage.removeItem(testKey);
done();
return;
}
sessionStorage.setItem(testKey, "started");
location.href = "?second";
});
</script>

View file

@ -0,0 +1,60 @@
<!doctype html>
<script src="../include.js"></script>
<script>
function queryFromURL(url) {
return new URL(url).search || "(none)";
}
function relevantEntryQueries(entries, queries) {
return entries
.filter(entry => queries.includes(queryFromURL(entry.url)))
.map(entry => `${queryFromURL(entry.url)}${entry.current ? "*" : ""}`)
.join(",");
}
function currentUIHistory() {
return JSON.parse(internals.dumpUIProcessSessionHistory());
}
function waitForPopState() {
return new Promise(resolve => addEventListener("popstate", resolve, { once: true }));
}
function dump(label, uiHistory) {
println(`${label}: location=${queryFromURL(location.href)}`);
println(`${label}: ui-current=${queryFromURL(uiHistory.currentURL)}`);
println(`${label}: ui-relevant-entries=${relevantEntryQueries(uiHistory.entries, ["?same-document-back-replaced", "?same-document-back-pushed"])}`);
println(`${label}: ui-webcontent-known-relevant-entries=${relevantEntryQueries(uiHistory.webContentKnownEntries, ["?same-document-back-replaced", "?same-document-back-pushed"])}`);
println(`${label}: ui-forward-button=${uiHistory.forwardButtonEnabled}`);
println(`${label}: ui-uses-ui-step-coordinates=${uiHistory.webContentUsesUIStepCoordinates}`);
}
asyncTest(async done => {
history.replaceState({ replaced: true }, "", "?same-document-back-replaced");
history.pushState({ pushed: true }, "", "?same-document-back-pushed");
await internals.flushSessionHistoryTraversalQueue();
const initialUIHistory = currentUIHistory();
if (!internals.dumpSessionHistory().includes("ui-process-session-history-same-document-back.html?same-document-back-pushed (current)")
|| queryFromURL(initialUIHistory.currentURL) !== "?same-document-back-pushed"
|| relevantEntryQueries(initialUIHistory.entries, ["?same-document-back-replaced", "?same-document-back-pushed"]) !== "?same-document-back-replaced,?same-document-back-pushed*"
|| !initialUIHistory.backButtonEnabled
|| initialUIHistory.forwardButtonEnabled)
println(`FAIL: unexpected initial UI history ${JSON.stringify(initialUIHistory)}`);
const popStatePromise = waitForPopState();
history.back();
await popStatePromise;
await internals.flushSessionHistoryTraversalQueue();
const uiHistory = currentUIHistory();
if (!internals.dumpSessionHistory().includes("ui-process-session-history-same-document-back.html?same-document-back-replaced (current)")
|| queryFromURL(uiHistory.currentURL) !== "?same-document-back-replaced"
|| relevantEntryQueries(uiHistory.entries, ["?same-document-back-replaced", "?same-document-back-pushed"]) !== "?same-document-back-replaced*,?same-document-back-pushed"
|| !uiHistory.forwardButtonEnabled)
println(`FAIL: unexpected UI history ${JSON.stringify(uiHistory)}`);
dump("after-back", uiHistory);
done();
});
</script>

View file

@ -0,0 +1,44 @@
<!doctype html>
<script src="../include.js"></script>
<script>
function queryFromURL(url) {
return new URL(url).search || "(none)";
}
function relevantEntryQueries(entries, queries) {
return entries
.filter(entry => queries.includes(queryFromURL(entry.url)))
.map(entry => `${queryFromURL(entry.url)}${entry.current ? "*" : ""}`)
.join(",");
}
function currentUIHistory() {
return JSON.parse(internals.dumpUIProcessSessionHistory());
}
function dump(label, uiHistory) {
println(`${label}: location=${queryFromURL(location.href)}`);
println(`${label}: ui-current=${queryFromURL(uiHistory.currentURL)}`);
println(`${label}: ui-relevant-entries=${relevantEntryQueries(uiHistory.entries, ["?same-document-replaced", "?same-document-pushed"])}`);
println(`${label}: ui-webcontent-known-relevant-entries=${relevantEntryQueries(uiHistory.webContentKnownEntries, ["?same-document-replaced", "?same-document-pushed"])}`);
println(`${label}: ui-buttons=${uiHistory.backButtonEnabled},${uiHistory.forwardButtonEnabled}`);
println(`${label}: ui-uses-ui-step-coordinates=${uiHistory.webContentUsesUIStepCoordinates}`);
}
asyncTest(async done => {
history.replaceState({ replaced: true }, "", "?same-document-replaced");
history.pushState({ pushed: true }, "", "?same-document-pushed");
await internals.flushSessionHistoryTraversalQueue();
const uiHistory = currentUIHistory();
if (!internals.dumpSessionHistory().includes("ui-process-session-history-same-document.html?same-document-pushed (current)")
|| queryFromURL(uiHistory.currentURL) !== "?same-document-pushed"
|| relevantEntryQueries(uiHistory.entries, ["?same-document-replaced", "?same-document-pushed"]) !== "?same-document-replaced,?same-document-pushed*"
|| !uiHistory.backButtonEnabled
|| uiHistory.forwardButtonEnabled)
println(`FAIL: unexpected UI history ${JSON.stringify(uiHistory)}`);
dump("after-push-state", uiHistory);
done();
});
</script>

View file

@ -46,6 +46,8 @@ void Application::create_platform_arguments(Core::ArgsParser& args_parser)
args_parser.add_option(test_dry_run, "List the tests that would be run, without running them", "dry-run");
args_parser.add_option(rebaseline, "Rebaseline any executed layout or text tests", "rebaseline");
args_parser.add_option(shuffle, "Shuffle the order of tests before running them", "shuffle", 's');
args_parser.add_option(run_ui_process_session_history_tests, "Run tests that require UI-process session history seeding",
"run-ui-process-session-history-tests");
args_parser.add_option(per_test_timeout_in_seconds, "Per-test timeout (default: 30)", "per-test-timeout", 't', "seconds");
args_parser.add_option(Core::ArgsParser::Option {
@ -85,6 +87,8 @@ void Application::create_platform_options(WebView::BrowserOptions& browser_optio
// Ensure consistent time zone operations across different machine configurations.
web_content_options.default_time_zone = "UTC"sv;
web_content_options.report_session_history_updates_in_test_mode = WebView::ReportSessionHistoryUpdatesInTestMode::Yes;
if (dump_gc_graph) {
// Force all tests to run in serial if we are interested in the GC graph.
test_concurrency = 1;

View file

@ -46,7 +46,7 @@ public:
bool test_dry_run { false };
bool rebaseline { false };
bool shuffle { false };
bool run_ui_process_session_history_tests { false };
int per_test_timeout_in_seconds { 30 };
u8 verbosity { 0 };

View file

@ -35,7 +35,17 @@ if (BUILD_TESTING)
COMMAND $<TARGET_FILE:test-web> --python-executable ${Python3_EXECUTABLE} --per-test-timeout 120 -v -v
)
add_test(
NAME LibWebUIProcessSessionHistory
COMMAND $<TARGET_FILE:test-web> --python-executable ${Python3_EXECUTABLE} --per-test-timeout 120
--force-new-process --run-ui-process-session-history-tests -f "Text/input/navigation/*"
--results-dir test-dumps/ui-process-session-history-results
)
set_tests_properties(LibWeb PROPERTIES
ENVIRONMENT LADYBIRD_SOURCE_DIR=${LADYBIRD_SOURCE_DIR}
TIMEOUT_SIGNAL_NAME SIGTERM)
set_tests_properties(LibWebUIProcessSessionHistory PROPERTIES
ENVIRONMENT LADYBIRD_SOURCE_DIR=${LADYBIRD_SOURCE_DIR}
TIMEOUT_SIGNAL_NAME SIGTERM)
endif()

View file

@ -6,6 +6,8 @@
#include "TestWebView.h"
#include "Application.h"
#include <LibCore/AnonymousBuffer.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ShareableBitmap.h>
@ -31,6 +33,11 @@ void TestWebView::clear_content_blockers()
client().async_set_content_blockers(m_client_state.page_index, MUST(Core::AnonymousBuffer::create_with_size(0)));
}
NonnullRefPtr<Core::Promise<Empty>> TestWebView::reset_session_history()
{
return WebView::ViewImplementation::reset_session_history_for_testing();
}
pid_t TestWebView::web_content_pid() const
{
return client().pid();

View file

@ -23,6 +23,7 @@ public:
static NonnullOwnPtr<TestWebView> create(Core::AnonymousBuffer theme, Web::DevicePixelSize window_size);
void clear_content_blockers();
NonnullRefPtr<Core::Promise<Empty>> reset_session_history();
pid_t web_content_pid() const;
NonnullRefPtr<Core::Promise<RefPtr<Gfx::Bitmap const>>> take_screenshot();
@ -35,7 +36,6 @@ private:
TestWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewport_size);
virtual void did_receive_screenshot(Badge<WebView::WebContentClient>, Gfx::ShareableBitmap const& screenshot) override;
RefPtr<Core::Promise<RefPtr<Gfx::Bitmap const>>> m_pending_screenshot;
NonnullRefPtr<TestPromise> m_test_promise;

View file

@ -158,6 +158,25 @@ static ErrorOr<void> skip_async_scrolling_tests_unless_enabled(Application const
return enumerate_test_files_recursively(path, s_skipped_tests);
}
static ErrorOr<void> skip_ui_process_session_history_tests_unless_enabled(Application const& app)
{
if (app.run_ui_process_session_history_tests)
return {};
static constexpr Array ui_process_session_history_tests {
"Text/input/navigation/ui-process-session-history-dump.html"sv,
"Text/input/navigation/ui-process-session-history-same-document-back.html"sv,
"Text/input/navigation/ui-process-session-history-same-document.html"sv,
};
for (auto const& test : ui_process_session_history_tests) {
auto path = LexicalPath::join(app.test_root_path, test).string();
s_skipped_tests.append(TRY(real_path_for_test_input(path)));
}
return {};
}
static void log_active_test_views(StringView reason)
{
outln();
@ -872,7 +891,6 @@ static void run_test(TestWebView& view, TestRunContext& context, size_t test_ind
};
// Clear the current document.
// FIXME: Implement a debug-request to do this more thoroughly.
auto promise = Core::Promise<Empty>::construct();
view.on_load_finish = [promise](auto const& url) {
@ -887,44 +905,47 @@ static void run_test(TestWebView& view, TestRunContext& context, size_t test_ind
view.on_test_finish = {};
promise->when_resolved([&view, test_index, &app, &context](auto) {
auto& test = context.tests[test_index];
test.did_start_test = true;
view.reset_session_history()->when_resolved([&view, test_index, &app, &context](auto) {
auto& test = context.tests[test_index];
test.did_start_test = true;
auto real_path = MUST(FileSystem::real_path(test.input_path));
auto headers_path = ByteString::formatted("{}.headers", real_path);
auto real_path = MUST(FileSystem::real_path(test.input_path));
auto headers_path = ByteString::formatted("{}.headers", real_path);
Optional<URL::URL> url;
if (FileSystem::exists(headers_path) || s_loaded_from_http_server.contains_slow(test.input_path)) {
// Some tests need to be served via the echo server so, for example, HTTP headers from .headers files are
// sent, or so that the resulting HTML document has a HTTP based origin (e.g for testing cookies).
auto echo_server_port = Application::web_content_options().echo_server_port;
VERIFY(echo_server_port.has_value());
auto relative_path = LexicalPath::relative_path(real_path, app.test_root_path);
VERIFY(relative_path.has_value());
url = URL::Parser::basic_parse(ByteString::formatted("http://{}:{}/static/{}", unique_localhost_hostname("test-web"sv), echo_server_port.value(), relative_path.value())).release_value();
} else {
url = URL::create_with_file_scheme(real_path).release_value();
}
Optional<URL::URL> url;
if (FileSystem::exists(headers_path) || s_loaded_from_http_server.contains_slow(test.input_path)) {
// Some tests need to be served via the echo server so, for example, HTTP headers from .headers
// files are sent, or so that the resulting HTML document has a HTTP based origin (e.g for testing
// cookies).
auto echo_server_port = Application::web_content_options().echo_server_port;
VERIFY(echo_server_port.has_value());
auto relative_path = LexicalPath::relative_path(real_path, app.test_root_path);
VERIFY(relative_path.has_value());
url = URL::Parser::basic_parse(ByteString::formatted("http://{}:{}/static/{}", unique_localhost_hostname("test-web"sv), echo_server_port.value(), relative_path.value())).release_value();
} else {
url = URL::create_with_file_scheme(real_path).release_value();
}
// Append variant query string if present (variant is "?foo=bar", set_query expects "foo=bar")
if (test.variant.has_value())
url->set_query(MUST(test.variant->substring_from_byte_offset_with_shared_superstring(1)));
// Append variant query string if present (variant is "?foo=bar", set_query expects "foo=bar")
if (test.variant.has_value())
url->set_query(MUST(test.variant->substring_from_byte_offset_with_shared_superstring(1)));
switch (test.mode) {
case TestMode::Crash:
case TestMode::Text:
case TestMode::Layout:
run_dump_test(view, context, test, *url);
return;
case TestMode::Ref:
run_ref_test(view, context, test, *url);
return;
case TestMode::Screenshot:
run_screenshot_test(view, context, test, *url);
return;
}
switch (test.mode) {
case TestMode::Crash:
case TestMode::Text:
case TestMode::Layout:
run_dump_test(view, context, test, *url);
return;
case TestMode::Ref:
run_ref_test(view, context, test, *url);
return;
case TestMode::Screenshot:
run_screenshot_test(view, context, test, *url);
return;
}
VERIFY_NOT_REACHED();
VERIFY_NOT_REACHED();
});
});
view.load(URL::about_blank());
@ -998,6 +1019,7 @@ static ErrorOr<int> run_tests(Core::AnonymousBuffer const& theme, Web::DevicePix
TRY(load_test_config(app.test_root_path));
TRY(skip_async_scrolling_tests_unless_enabled(app));
TRY(skip_ui_process_session_history_tests_unless_enabled(app));
Vector<Test> tests;