From 9a5b31ccd1f843809febbdcc7abf75901d6f1b1b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 6 Jun 2025 20:11:13 -0400 Subject: [PATCH] LibWebView+Tests+UI: Migrate headless-browser to test-web Now that headless mode is built into the main Ladybird executable, the headless-browser's only purpose is to run tests. So let's move it to the testing directory and rename it to test-web (a la test-js / test-wasm). --- Documentation/BuildInstructionsLadybird.md | 4 +- Documentation/Porting.md | 3 +- Documentation/Testing.md | 6 +- Libraries/LibWebView/Utilities.cpp | 11 ++- Libraries/LibWebView/Utilities.h | 3 +- Meta/WPT.sh | 6 +- Meta/ladybird.py | 1 - Tests/LibWeb/CMakeLists.txt | 4 +- Tests/LibWeb/add_libweb_test.py | 3 +- .../LibWeb/test-web}/Application.cpp | 40 +++------- .../LibWeb/test-web}/Application.h | 18 +++-- Tests/LibWeb/test-web/CMakeLists.txt | 25 +++++++ .../LibWeb/test-web}/Fixture.cpp | 26 ++++--- .../LibWeb/test-web}/Fixture.h | 3 +- .../Test.h => Tests/LibWeb/test-web/TestWeb.h | 32 +------- .../LibWeb/test-web/TestWebView.cpp | 19 ++--- .../LibWeb/test-web/TestWebView.h | 11 +-- .../LibWeb/test-web/main.cpp | 75 +++++++++++++++---- UI/CMakeLists.txt | 5 +- UI/Headless/CMakeLists.txt | 20 ----- UI/Headless/main.cpp | 40 ---------- 21 files changed, 166 insertions(+), 189 deletions(-) rename {UI/Headless => Tests/LibWeb/test-web}/Application.cpp (72%) rename {UI/Headless => Tests/LibWeb/test-web}/Application.h (95%) create mode 100644 Tests/LibWeb/test-web/CMakeLists.txt rename {UI/Headless => Tests/LibWeb/test-web}/Fixture.cpp (84%) rename {UI/Headless => Tests/LibWeb/test-web}/Fixture.h (93%) rename UI/Headless/Test.h => Tests/LibWeb/test-web/TestWeb.h (55%) rename UI/Headless/HeadlessWebView.cpp => Tests/LibWeb/test-web/TestWebView.cpp (59%) rename UI/Headless/HeadlessWebView.h => Tests/LibWeb/test-web/TestWebView.h (72%) rename UI/Headless/Test.cpp => Tests/LibWeb/test-web/main.cpp (91%) delete mode 100644 UI/Headless/CMakeLists.txt delete mode 100644 UI/Headless/main.cpp diff --git a/Documentation/BuildInstructionsLadybird.md b/Documentation/BuildInstructionsLadybird.md index a2a1197d8a5..8aac921ba6e 100644 --- a/Documentation/BuildInstructionsLadybird.md +++ b/Documentation/BuildInstructionsLadybird.md @@ -200,8 +200,8 @@ BUILD_PRESET=Debug ./Meta/ladybird.py run Note that debug symbols are available in both Release and Debug builds. -If you want to run other applications, such as the headless-browser, the JS REPL, or the WebAssembly REPL, specify an -executable with `./Meta/ladybird.py run `. +If you want to run other applications, such as the the JS REPL or the WebAssembly REPL, specify an executable with +`./Meta/ladybird.py run `. ### The User Interfaces diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 183d6df1e1e..1cc40d48444 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -13,11 +13,10 @@ There are two types of ports that can be made to Ladybird: ### UI Ports -There are currently three supported UI ports: +There are currently two supported UI ports: - Qt6: The generic UI port. - AppKit/Cocoa: The macOS native port, which uses the AppKit framework. -- Headless: A headless port that does not have a UI, used for testing. ### Platform Ports diff --git a/Documentation/Testing.md b/Documentation/Testing.md index 491cf95705b..bfe7f047881 100644 --- a/Documentation/Testing.md +++ b/Documentation/Testing.md @@ -13,8 +13,8 @@ Tests of internal C++ code go in their own `TestFoo.cpp` file in `Tests/LibWeb`. The easiest way to run tests is to use the `ladybird.py` script. The LibWeb tests are registered with CMake as a test in `UI/CMakeLists.txt`. Using the built-in test filtering, you can run all tests with `Meta/ladybird.py test` or run -just the LibWeb tests with `Meta/ladybird.py test LibWeb`. The second way is to invoke the headless browser test runner -directly. See the invocation in `UI/CMakeLists.txt` for the expected command line arguments. +just the LibWeb tests with `Meta/ladybird.py test LibWeb`. The second way is to invoke the `test-web` test runner +directly with `Meta/ladybird.py run test-web`. A third way is to invoke `ctest` directly. The simplest method is to use the `default` preset from `CMakePresets.json`: @@ -137,7 +137,7 @@ you will need to regenerate the corresponding expectations file to match the act For Text or Layout tests, you can "rebaseline" the tests to regenerate the expectation file: ```bash -./Meta/ladybird.py run headless-browser --run-tests "./Tests/LibWeb" --rebaseline -f Text/input/your-new-test-name.html +./Meta/ladybird.py run test-web --rebaseline -f Text/input/your-new-test-name.html ``` For Ref and Screenshot tests, you will need to supply the equivalently rendering HTML manually. Though for Screenshot diff --git a/Libraries/LibWebView/Utilities.cpp b/Libraries/LibWebView/Utilities.cpp index 210b1e7bc66..6628501329e 100644 --- a/Libraries/LibWebView/Utilities.cpp +++ b/Libraries/LibWebView/Utilities.cpp @@ -28,6 +28,7 @@ static constexpr auto libexec_path = "libexec"sv; #endif ByteString s_ladybird_resource_root; +static Optional s_ladybird_binary_path; Optional s_mach_server_name; @@ -43,8 +44,11 @@ void set_mach_server_name(ByteString name) s_mach_server_name = move(name); } -ErrorOr application_directory() +static ErrorOr application_directory() { + if (s_ladybird_binary_path.has_value()) + return *s_ladybird_binary_path; + auto current_executable_path = TRY(Core::System::current_executable_path()); return LexicalPath::dirname(current_executable_path); } @@ -61,8 +65,10 @@ static LexicalPath find_prefix(LexicalPath const& application_directory) return application_directory.parent(); } -void platform_init() +void platform_init(Optional ladybird_binary_path) { + s_ladybird_binary_path = move(ladybird_binary_path); + s_ladybird_resource_root = [] { auto home = Core::Environment::get("XDG_CONFIG_HOME"sv) .value_or_lazy_evaluated_optional([]() { return Core::Environment::get("HOME"sv); }); @@ -78,6 +84,7 @@ void platform_init() return find_prefix(LexicalPath(app_dir)).append("share/Lagom"sv).string(); #endif }(); + Core::ResourceImplementation::install(make(MUST(String::from_byte_string(s_ladybird_resource_root)))); } diff --git a/Libraries/LibWebView/Utilities.h b/Libraries/LibWebView/Utilities.h index 7d002a857b9..94fd0739598 100644 --- a/Libraries/LibWebView/Utilities.h +++ b/Libraries/LibWebView/Utilities.h @@ -14,9 +14,8 @@ namespace WebView { -void platform_init(); +void platform_init(Optional ladybird_binary_path = {}); void copy_default_config_files(StringView config_path); -ErrorOr application_directory(); ErrorOr> get_paths_for_helper_process(StringView process_name); extern ByteString s_ladybird_resource_root; diff --git a/Meta/WPT.sh b/Meta/WPT.sh index 56ea0ce91a3..97ffeb8f812 100755 --- a/Meta/WPT.sh +++ b/Meta/WPT.sh @@ -73,7 +73,7 @@ ensure_run_dir() { LADYBIRD_BINARY=${LADYBIRD_BINARY:-"$(default_binary_path)/Ladybird"} WEBDRIVER_BINARY=${WEBDRIVER_BINARY:-"$(default_binary_path)/WebDriver"} -HEADLESS_BROWSER_BINARY=${HEADLESS_BROWSER_BINARY:-"$(default_binary_path)/headless-browser"} +TEST_WEB_BINARY=${TEST_WEB_BINARY:-"${BUILD_DIR}/bin/test-web"} WPT_PROCESSES=${WPT_PROCESSES:-$(get_number_of_processing_units)} WPT_CERTIFICATES=( "tools/certs/cacert.pem" @@ -596,14 +596,14 @@ import_wpt() done < <(printf "%s\n" "${RAW_TESTS[@]}" | sort -u) pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null - ./Meta/ladybird.py build headless-browser + ./Meta/ladybird.py build test-web set +e for path in "${TESTS[@]}"; do echo "Importing test from ${path}" if ! ./Meta/import-wpt-test.py https://wpt.live/"${path}"; then continue fi - "${HEADLESS_BROWSER_BINARY}" --run-tests ./Tests/LibWeb --rebaseline -f "$path" + "${TEST_WEB_BINARY}" --rebaseline -f "$path" done set -e popd > /dev/null diff --git a/Meta/ladybird.py b/Meta/ladybird.py index 99349f27bf3..fb6404f7227 100755 --- a/Meta/ladybird.py +++ b/Meta/ladybird.py @@ -341,7 +341,6 @@ def run_main(host_system: HostSystem, build_dir: Path, target: str, args: list[s run_args = [] if host_system == HostSystem.macOS and target in ( - "headless-browser", "ImageDecoder", "Ladybird", "RequestServer", diff --git a/Tests/LibWeb/CMakeLists.txt b/Tests/LibWeb/CMakeLists.txt index 92c7f3c5bcf..4330b0632bd 100644 --- a/Tests/LibWeb/CMakeLists.txt +++ b/Tests/LibWeb/CMakeLists.txt @@ -1,8 +1,8 @@ set(TEST_SOURCES TestCSSIDSpeed.cpp + TestCSSInheritedProperty.cpp TestCSSPixels.cpp TestCSSTokenStream.cpp - TestCSSInheritedProperty.cpp TestFetchInfrastructure.cpp TestFetchURL.cpp TestHTMLTokenizer.cpp @@ -33,3 +33,5 @@ if (ENABLE_SWIFT) target_link_libraries(TestLibWebSwift PRIVATE AK LibWeb LibGC SwiftTesting::SwiftTesting) add_test(NAME TestLibWebSwift COMMAND TestLibWebSwift) endif() + +add_subdirectory("test-web") diff --git a/Tests/LibWeb/add_libweb_test.py b/Tests/LibWeb/add_libweb_test.py index 991c0353ce5..d799e31d2aa 100755 --- a/Tests/LibWeb/add_libweb_test.py +++ b/Tests/LibWeb/add_libweb_test.py @@ -87,8 +87,7 @@ def create_test(test_name: str, test_type: str, is_async: bool = False) -> None: elif test_type == "Layout": input_boilerplate = generic_boilerplate expected_boilerplate = f"""run - ./Meta/ladybird.py run headless-browser --run-tests - "${{LADYBIRD_SOURCE_DIR}}/Tests/LibWeb" --rebaseline -f {input_file} + ./Meta/ladybird.py run test-web --rebaseline -f {input_file} to produce the expected output for this test """ print("Delete and replace it with if test should run in quirks mode") diff --git a/UI/Headless/Application.cpp b/Tests/LibWeb/test-web/Application.cpp similarity index 72% rename from UI/Headless/Application.cpp rename to Tests/LibWeb/test-web/Application.cpp index 83594ea81c9..4b818bc4dde 100644 --- a/UI/Headless/Application.cpp +++ b/Tests/LibWeb/test-web/Application.cpp @@ -4,21 +4,22 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "Application.h" +#include "Fixture.h" + #include #include #include #include -#include -#include -namespace Ladybird { +namespace TestWeb { Application::Application(Badge, Main::Arguments&) - : resources_folder(WebView::s_ladybird_resource_root) - , test_concurrency(Core::System::hardware_concurrency()) + : test_concurrency(Core::System::hardware_concurrency()) , python_executable_path("python3") - { + if (auto ladybird_source_dir = Core::Environment::get("LADYBIRD_SOURCE_DIR"sv); ladybird_source_dir.has_value()) + test_root_path = LexicalPath::join(*ladybird_source_dir, "Tests"sv, "LibWeb"sv).string(); } Application::~Application() @@ -29,37 +30,16 @@ Application::~Application() void Application::create_platform_arguments(Core::ArgsParser& args_parser) { + args_parser.add_option(test_root_path, "Path containing the tests to run", "test-path", 0, "path"); args_parser.add_option(test_concurrency, "Maximum number of tests to run at once", "test-concurrency", 'j', "jobs"); - args_parser.add_option(python_executable_path, "Path to python3", "python-executable", 'P', "path"); args_parser.add_option(test_globs, "Only run tests matching the given glob", "filter", 'f', "glob"); - args_parser.add_option(test_dry_run, "List the tests that would be run, without running them", "dry-run"); + args_parser.add_option(python_executable_path, "Path to python3", "python-executable", 'P', "path"); args_parser.add_option(dump_failed_ref_tests, "Dump screenshots of failing ref tests", "dump-failed-ref-tests", 'D'); args_parser.add_option(dump_gc_graph, "Dump GC graph", "dump-gc-graph", 'G'); - args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path"); + 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(per_test_timeout_in_seconds, "Per-test timeout (default: 30)", "per-test-timeout", 't', "seconds"); - args_parser.add_option(Core::ArgsParser::Option { - .argument_mode = Core::ArgsParser::OptionArgumentMode::Optional, - .help_string = "Run tests. If a path is provided, tests are loaded from that path. Otherwise, LADYBIRD_SOURCE_DIR must be set.", - .long_name = "run-tests", - .short_name = 'R', - .value_name = "test-root-path", - .accept_value = [&](StringView value) -> ErrorOr { - if (!value.is_empty()) { - test_root_path = value; - return true; - } - - if (auto ladybird_source_dir = Core::Environment::get("LADYBIRD_SOURCE_DIR"sv); ladybird_source_dir.has_value()) { - test_root_path = LexicalPath::join(*ladybird_source_dir, "Tests"sv, "LibWeb"sv).string(); - return true; - } - - return false; - }, - }); - args_parser.add_option(Core::ArgsParser::Option { .argument_mode = Core::ArgsParser::OptionArgumentMode::Optional, .help_string = "Log extra information about test results (use multiple times for more information)", diff --git a/UI/Headless/Application.h b/Tests/LibWeb/test-web/Application.h similarity index 95% rename from UI/Headless/Application.h rename to Tests/LibWeb/test-web/Application.h index 84a6645ee79..494f81cbdba 100644 --- a/UI/Headless/Application.h +++ b/Tests/LibWeb/test-web/Application.h @@ -11,7 +11,7 @@ #include #include -namespace Ladybird { +namespace TestWeb { class Application : public WebView::Application { WEB_VIEW_APPLICATION(Application) @@ -33,17 +33,21 @@ public: static constexpr u8 VERBOSITY_LEVEL_LOG_SLOWEST_TESTS = 2; static constexpr u8 VERBOSITY_LEVEL_LOG_SKIPPED_TESTS = 3; - ByteString resources_folder; + ByteString test_root_path; + size_t test_concurrency { 1 }; + Vector test_globs; + + ByteString python_executable_path; + bool dump_failed_ref_tests { false }; bool dump_gc_graph { false }; - size_t test_concurrency { 1 }; - ByteString python_executable_path; - ByteString test_root_path; - Vector test_globs; + bool test_dry_run { false }; bool rebaseline { false }; - u8 verbosity { 0 }; + int per_test_timeout_in_seconds { 30 }; + + u8 verbosity { 0 }; }; } diff --git a/Tests/LibWeb/test-web/CMakeLists.txt b/Tests/LibWeb/test-web/CMakeLists.txt new file mode 100644 index 00000000000..ec6e37a1f5f --- /dev/null +++ b/Tests/LibWeb/test-web/CMakeLists.txt @@ -0,0 +1,25 @@ +set(SOURCES + Application.cpp + Fixture.cpp + TestWebView.cpp + main.cpp +) + +add_executable(test-web ${SOURCES}) +add_dependencies(test-web ladybird_build_resource_files ImageDecoder RequestServer WebContent WebWorker) +target_link_libraries(test-web PRIVATE AK LibCore LibDiff LibFileSystem LibGfx LibImageDecoderClient LibIPC LibJS LibMain LibRequests LibURL LibWeb LibWebView) + +if (APPLE) + target_compile_definitions(test-web PRIVATE LADYBIRD_BINARY_PATH="$") +endif() + +if (BUILD_TESTING) + find_package(Python3 REQUIRED) + + add_test( + NAME LibWeb + COMMAND $ --python-executable ${Python3_EXECUTABLE} --dump-failed-ref-tests --per-test-timeout 120 --verbose + ) + + set_tests_properties(LibWeb PROPERTIES ENVIRONMENT LADYBIRD_SOURCE_DIR=${SERENITY_PROJECT_ROOT}) +endif() diff --git a/UI/Headless/Fixture.cpp b/Tests/LibWeb/test-web/Fixture.cpp similarity index 84% rename from UI/Headless/Fixture.cpp rename to Tests/LibWeb/test-web/Fixture.cpp index 1a32a3573d5..599619ed43f 100644 --- a/UI/Headless/Fixture.cpp +++ b/Tests/LibWeb/test-web/Fixture.cpp @@ -4,14 +4,15 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "Fixture.h" +#include "Application.h" + #include #include #include #include -#include -#include -namespace Ladybird { +namespace TestWeb { static ByteString s_fixtures_path; @@ -45,22 +46,24 @@ private: Optional m_process; }; -#ifdef AK_OS_WINDOWS -// FIXME: Implement Ladybird::HttpEchoServerFixture::setup on Windows +#if defined(AK_OS_WINDOWS) + ErrorOr HttpEchoServerFixture::setup(WebView::WebContentOptions&) { - VERIFY(0 && "Ladybird::HttpEchoServerFixture::setup is not implemented"); + VERIFY(0 && "HttpEchoServerFixture::setup is not implemented"); } -// FIXME: Implement Ladybird::HttpEchoServerFixture::teardown_impl on Windows + void HttpEchoServerFixture::teardown_impl() { - VERIFY(0 && "Ladybird::HttpEchoServerFixture::teardown_impl is not implemented"); + VERIFY(0 && "HttpEchoServerFixture::teardown_impl is not implemented"); } + #else + ErrorOr HttpEchoServerFixture::setup(WebView::WebContentOptions& web_content_options) { auto const script_path = LexicalPath::join(s_fixtures_path, m_script_path); - auto const arguments = Vector { script_path.string(), "--directory", Ladybird::Application::the().test_root_path }; + auto const arguments = Vector { script_path.string(), "--directory", Application::the().test_root_path }; // FIXME: Pick a more reasonable log path that is more observable auto const log_path = LexicalPath::join(Core::StandardPaths::tempfile_directory(), "http-test-server.log"sv).string(); @@ -68,7 +71,7 @@ ErrorOr HttpEchoServerFixture::setup(WebView::WebContentOptions& web_conte auto stdout_fds = TRY(Core::System::pipe2(0)); auto const process_options = Core::ProcessSpawnOptions { - .executable = Ladybird::Application::the().python_executable_path, + .executable = Application::the().python_executable_path, .search_for_executable_in_path = true, .arguments = arguments, .file_actions = { @@ -111,11 +114,12 @@ void HttpEchoServerFixture::teardown_impl() m_process = {}; } + #endif void Fixture::initialize_fixtures() { - s_fixtures_path = LexicalPath::join(Ladybird::Application::the().test_root_path, "Fixtures"sv).string(); + s_fixtures_path = LexicalPath::join(Application::the().test_root_path, "Fixtures"sv).string(); auto& registry = all(); registry.append(make()); diff --git a/UI/Headless/Fixture.h b/Tests/LibWeb/test-web/Fixture.h similarity index 93% rename from UI/Headless/Fixture.h rename to Tests/LibWeb/test-web/Fixture.h index ea461ace662..b306d108d22 100644 --- a/UI/Headless/Fixture.h +++ b/Tests/LibWeb/test-web/Fixture.h @@ -11,8 +11,9 @@ #include #include #include +#include -namespace Ladybird { +namespace TestWeb { class Fixture { public: diff --git a/UI/Headless/Test.h b/Tests/LibWeb/test-web/TestWeb.h similarity index 55% rename from UI/Headless/Test.h rename to Tests/LibWeb/test-web/TestWeb.h index 8f5bd3b82c9..a253cf0d65f 100644 --- a/UI/Headless/Test.h +++ b/Tests/LibWeb/test-web/TestWeb.h @@ -1,27 +1,19 @@ /* - * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2024-2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include #include -#include #include #include -#include #include -#include #include #include -#include -#include -namespace Ladybird { - -class HeadlessWebView; +namespace TestWeb { enum class TestMode { Layout, @@ -43,23 +35,6 @@ enum class RefTestExpectationType { Mismatch, }; -static constexpr StringView test_result_to_string(TestResult result) -{ - switch (result) { - case TestResult::Pass: - return "Pass"sv; - case TestResult::Fail: - return "Fail"sv; - case TestResult::Skipped: - return "Skipped"sv; - case TestResult::Timeout: - return "Timeout"sv; - case TestResult::Crashed: - return "Crashed"sv; - } - VERIFY_NOT_REACHED(); -} - struct Test { TestMode mode; @@ -88,7 +63,4 @@ struct TestCompletion { using TestPromise = Core::Promise; -ErrorOr run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize window_size); -void run_dump_test(HeadlessWebView&, Test&, URL::URL const&, int timeout_in_milliseconds); - } diff --git a/UI/Headless/HeadlessWebView.cpp b/Tests/LibWeb/test-web/TestWebView.cpp similarity index 59% rename from UI/Headless/HeadlessWebView.cpp rename to Tests/LibWeb/test-web/TestWebView.cpp index 91dd8c2a365..71fc5caca5b 100644 --- a/UI/Headless/HeadlessWebView.cpp +++ b/Tests/LibWeb/test-web/TestWebView.cpp @@ -4,32 +4,33 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "TestWebView.h" + #include #include -#include -namespace Ladybird { +namespace TestWeb { -NonnullOwnPtr HeadlessWebView::create(Core::AnonymousBuffer theme, Web::DevicePixelSize window_size) +NonnullOwnPtr TestWebView::create(Core::AnonymousBuffer theme, Web::DevicePixelSize window_size) { - auto view = adopt_own(*new HeadlessWebView(move(theme), window_size)); + auto view = adopt_own(*new TestWebView(move(theme), window_size)); view->initialize_client(CreateNewClient::Yes); return view; } -HeadlessWebView::HeadlessWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewport_size) +TestWebView::TestWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewport_size) : WebView::HeadlessWebView(move(theme), viewport_size) , m_test_promise(TestPromise::construct()) { } -void HeadlessWebView::clear_content_filters() +void TestWebView::clear_content_filters() { client().async_set_content_filters(m_client_state.page_index, {}); } -NonnullRefPtr>> HeadlessWebView::take_screenshot() +NonnullRefPtr>> TestWebView::take_screenshot() { VERIFY(!m_pending_screenshot); @@ -39,7 +40,7 @@ NonnullRefPtr>> HeadlessWebView::take_sc return *m_pending_screenshot; } -void HeadlessWebView::did_receive_screenshot(Badge, Gfx::ShareableBitmap const& screenshot) +void TestWebView::did_receive_screenshot(Badge, Gfx::ShareableBitmap const& screenshot) { VERIFY(m_pending_screenshot); @@ -47,7 +48,7 @@ void HeadlessWebView::did_receive_screenshot(Badge, G pending_screenshot->resolve(screenshot.bitmap()); } -void HeadlessWebView::on_test_complete(TestCompletion completion) +void TestWebView::on_test_complete(TestCompletion completion) { m_pending_screenshot.clear(); m_pending_dialog = Web::Page::PendingDialog::None; diff --git a/UI/Headless/HeadlessWebView.h b/Tests/LibWeb/test-web/TestWebView.h similarity index 72% rename from UI/Headless/HeadlessWebView.h rename to Tests/LibWeb/test-web/TestWebView.h index 237f88e312b..0ea3790494a 100644 --- a/UI/Headless/HeadlessWebView.h +++ b/Tests/LibWeb/test-web/TestWebView.h @@ -6,6 +6,8 @@ #pragma once +#include "TestWeb.h" + #include #include #include @@ -13,13 +15,12 @@ #include #include #include -#include -namespace Ladybird { +namespace TestWeb { -class HeadlessWebView final : public WebView::HeadlessWebView { +class TestWebView final : public WebView::HeadlessWebView { public: - static NonnullOwnPtr create(Core::AnonymousBuffer theme, Web::DevicePixelSize window_size); + static NonnullOwnPtr create(Core::AnonymousBuffer theme, Web::DevicePixelSize window_size); void clear_content_filters(); @@ -29,7 +30,7 @@ public: void on_test_complete(TestCompletion); private: - HeadlessWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewport_size); + TestWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewport_size); virtual void did_receive_screenshot(Badge, Gfx::ShareableBitmap const& screenshot) override; diff --git a/UI/Headless/Test.cpp b/Tests/LibWeb/test-web/main.cpp similarity index 91% rename from UI/Headless/Test.cpp rename to Tests/LibWeb/test-web/main.cpp index c80c584e3d1..e20a54b1f02 100644 --- a/UI/Headless/Test.cpp +++ b/Tests/LibWeb/test-web/main.cpp @@ -1,11 +1,17 @@ /* - * Copyright (c) 2024-2025, Tim Flynn + * Copyright (c) 2022, Dex♪ + * Copyright (c) 2023-2025, Tim Flynn + * Copyright (c) 2023, Andreas Kling + * Copyright (c) 2023-2024, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ +#include "Application.h" +#include "TestWeb.h" +#include "TestWebView.h" + #include -#include #include #include #include @@ -21,16 +27,32 @@ #include #include #include +#include #include #include -#include -#include -#include +#include -namespace Ladybird { +namespace TestWeb { static Vector s_skipped_tests; +static constexpr StringView test_result_to_string(TestResult result) +{ + switch (result) { + case TestResult::Pass: + return "Pass"sv; + case TestResult::Fail: + return "Fail"sv; + case TestResult::Skipped: + return "Skipped"sv; + case TestResult::Timeout: + return "Timeout"sv; + case TestResult::Crashed: + return "Crashed"sv; + } + VERIFY_NOT_REACHED(); +} + static ErrorOr load_test_config(StringView test_root_path) { auto config_path = LexicalPath::join(test_root_path, "TestConfig.ini"sv); @@ -127,14 +149,14 @@ static ErrorOr collect_crash_tests(Application const& app, Vector& t return {}; } -static void clear_test_callbacks(HeadlessWebView& view) +static void clear_test_callbacks(TestWebView& view) { view.on_load_finish = {}; view.on_test_finish = {}; view.on_web_content_crashed = {}; } -void run_dump_test(HeadlessWebView& view, Test& test, URL::URL const& url, int timeout_in_milliseconds) +static void run_dump_test(TestWebView& view, Test& test, URL::URL const& url, int timeout_in_milliseconds) { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { view.on_load_finish = {}; @@ -307,7 +329,7 @@ if (!hasReftestWaitClass()) { } )"_string; -static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, int timeout_in_milliseconds) +static void run_ref_test(TestWebView& view, Test& test, URL::URL const& url, int timeout_in_milliseconds) { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { view.on_load_finish = {}; @@ -404,7 +426,7 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, timer->start(); } -static void run_test(HeadlessWebView& view, Test& test, Application& app) +static void run_test(TestWebView& view, Test& test, Application& app) { // Clear the current document. // FIXME: Implement a debug-request to do this more thoroughly. @@ -441,7 +463,7 @@ static void run_test(HeadlessWebView& view, Test& test, Application& app) view.load(URL::about_blank()); } -static void set_ui_callbacks_for_tests(HeadlessWebView& view) +static void set_ui_callbacks_for_tests(TestWebView& view) { view.on_request_file_picker = [&](auto const& accepted_file_types, auto allow_multiple_files) { // Create some dummy files for tests. @@ -490,7 +512,7 @@ static void set_ui_callbacks_for_tests(HeadlessWebView& view) }; } -ErrorOr run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize window_size) +static ErrorOr run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize window_size) { auto& app = Application::the(); TRY(load_test_config(app.test_root_path)); @@ -539,11 +561,11 @@ ErrorOr run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize auto concurrency = min(app.test_concurrency, tests.size()); size_t loaded_web_views = 0; - Vector> views; + Vector> views; views.ensure_capacity(concurrency); for (size_t i = 0; i < concurrency; ++i) { - auto view = HeadlessWebView::create(theme, window_size); + auto view = TestWebView::create(theme, window_size); view->on_load_finish = [&](auto const&) { ++loaded_web_views; }; views.unchecked_append(move(view)); @@ -685,3 +707,28 @@ ErrorOr run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize } } + +ErrorOr serenity_main(Main::Arguments arguments) +{ +#if defined(LADYBIRD_BINARY_PATH) + WebView::platform_init(LADYBIRD_BINARY_PATH); +#else + WebView::platform_init(); +#endif + + auto app = TestWeb::Application::create(arguments); + TRY(app->launch_services()); + + auto theme_path = LexicalPath::join(WebView::s_ladybird_resource_root, "themes"sv, "Default.ini"sv); + auto theme = TRY(Gfx::load_system_theme(theme_path.string())); + + auto const& browser_options = TestWeb::Application::browser_options(); + Web::DevicePixelSize window_size { browser_options.window_width, browser_options.window_height }; + + VERIFY(!app->test_root_path.is_empty()); + + app->test_root_path = LexicalPath::absolute_path(TRY(FileSystem::current_working_directory()), app->test_root_path); + TRY(app->launch_test_fixtures()); + + return TestWeb::run_tests(theme, window_size); +} diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index a7c1298ebaa..506af35bd9f 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -104,17 +104,14 @@ else() ) endif() -add_subdirectory(Headless) - set(ladybird_helper_processes ImageDecoder RequestServer WebContent WebWorker) add_dependencies(ladybird ${ladybird_helper_processes}) -add_dependencies(headless-browser ${ladybird_helper_processes} ladybird_build_resource_files) add_dependencies(WebDriver ladybird) set_helper_process_properties(${ladybird_helper_processes}) if (APPLE) - set_helper_process_properties(headless-browser WebDriver) + set_helper_process_properties(WebDriver) endif() if(NOT CMAKE_SKIP_INSTALL_RULES) diff --git a/UI/Headless/CMakeLists.txt b/UI/Headless/CMakeLists.txt deleted file mode 100644 index 1cbbe06ac24..00000000000 --- a/UI/Headless/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -set(SOURCES - Application.cpp - Fixture.cpp - HeadlessWebView.cpp - Test.cpp - main.cpp -) - -add_executable(headless-browser ${SOURCES}) -target_include_directories(headless-browser PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(headless-browser PRIVATE ${LADYBIRD_SOURCE_DIR}) -target_link_libraries(headless-browser PRIVATE ${LADYBIRD_LIBS} LibDiff) - -if (BUILD_TESTING) - find_package(Python3 REQUIRED) - add_test( - NAME LibWeb - COMMAND $ --run-tests ${LADYBIRD_SOURCE_DIR}/Tests/LibWeb --python-executable ${Python3_EXECUTABLE} --dump-failed-ref-tests --per-test-timeout 120 --verbose - ) -endif() diff --git a/UI/Headless/main.cpp b/UI/Headless/main.cpp deleted file mode 100644 index e0245745d9e..00000000000 --- a/UI/Headless/main.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022, Dex♪ - * Copyright (c) 2023-2025, Tim Flynn - * Copyright (c) 2023, Andreas Kling - * Copyright (c) 2023-2024, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -ErrorOr serenity_main(Main::Arguments arguments) -{ - WebView::platform_init(); - - auto app = Ladybird::Application::create(arguments); - TRY(app->launch_services()); - - Core::ResourceImplementation::install(make(MUST(String::from_byte_string(app->resources_folder)))); - - auto theme_path = LexicalPath::join(app->resources_folder, "themes"sv, "Default.ini"sv); - auto theme = TRY(Gfx::load_system_theme(theme_path.string())); - - auto const& browser_options = Ladybird::Application::browser_options(); - Web::DevicePixelSize window_size { browser_options.window_width, browser_options.window_height }; - - VERIFY(!app->test_root_path.is_empty()); - - app->test_root_path = LexicalPath::absolute_path(TRY(FileSystem::current_working_directory()), app->test_root_path); - TRY(app->launch_test_fixtures()); - - return Ladybird::run_tests(theme, window_size); -}