diff --git a/.gitignore b/.gitignore index 66fc22b56..31e001b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,25 @@ /.zig-cache/ /.lp-cache/ +/.lp-cache-win/ zig-out lightpanda.id /src/html5ever/target/ +/html5ever-test/ src/snapshot.bin +/_link_trace.log +/_msvc_link.log +/_msvc_use_lld_false.log +/_msvc_use_lld_false_rel.log +/tmp-browser-smoke/**/*.stdout.txt +/tmp-browser-smoke/**/*.stderr.txt +/tmp-browser-smoke/**/*.out.txt +/tmp-browser-smoke/**/*.err.txt +/tmp-browser-smoke/**/*.before.png +/tmp-browser-smoke/headed-smoke.png +/tmp-browser-smoke/links.bmp +/tmp-browser-smoke/flow-layout/flow-layout.png +/tmp-browser-smoke/image-smoke/headed-image-smoke.png +/tmp-browser-smoke/inline-flow/inline-flow.png +/tmp-browser-smoke/multi-image/multi-image.png +/tmp-browser-smoke/wrapped-link/wrapped-before.png +/tmp-browser-smoke/wrapped-link/wrapped-base-before.png diff --git a/README.md b/README.md index 1a860fc74..f7aaf372b 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,35 @@ INFO app : server running . . . . . . . . . . . . . . . . . [+0ms] address = 127.0.0.1:9222 ``` +### Browser mode switch (fork) + +This fork adds a browser mode switch on `fetch` and `serve`: + +```console +./lightpanda serve --browser_mode headed +``` + +Shortcuts are available: +- `--headed` +- `--headless` + +`headed` is currently experimental. +- On Windows targets, it starts a native headed window lifecycle backend. +- Windows headed mode now forwards native mouse (down/up/move/wheel/hwheel), click, keydown/keyup (including repeat state), text input (`WM_CHAR`/`WM_UNICHAR`), IME result/preedit composition messages (`WM_IME_COMPOSITION`), back/forward mouse buttons, and window blur into page input, with caret-aware text insertion, `Ctrl/Meta + A` select-all, word-wise caret/edit shortcuts (`Ctrl/Meta + ArrowLeft/ArrowRight`, `Ctrl/Meta + Backspace/Delete`), textarea vertical/line navigation (`ArrowUp/ArrowDown`, line-aware `Home/End`, document `Ctrl/Meta + Home/End`), `Tab`/`Shift+Tab` focus traversal (including positive `tabindex` ordering), and native clipboard shortcuts (`Ctrl/Meta + C/X/V`, `Ctrl+Insert`, `Shift+Insert`, `Shift+Delete`) for text controls. Clipboard shortcuts now dispatch cancelable `copy`/`cut`/`paste` events first and respect `preventDefault()`. +- On non-Windows targets, it safely falls back to headless execution. + +Viewport sizing is configurable for both modes: +- `--window_width ` +- `--window_height ` + +CDP viewport control is also wired in this fork: +- `Emulation.setDeviceMetricsOverride` +- `Emulation.clearDeviceMetricsOverride` +- `Browser.setWindowBounds` (width/height) + +Windows run/build guide for this fork: +- [`docs/WINDOWS_FULL_USE.md`](docs/WINDOWS_FULL_USE.md) + Once the CDP server started, you can run a Puppeteer script by configuring the `browserWSEndpoint`. diff --git a/build.zig b/build.zig index 7e3a28177..9dcd89dd4 100644 --- a/build.zig +++ b/build.zig @@ -19,10 +19,19 @@ const std = @import("std"); const Build = std.Build; +const TargetClass = enum { + hosted, + bare_metal, +}; pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const is_msvc = target.result.abi == .msvc; + const target_class: TargetClass = b.option(TargetClass, "target_class", "Build class: hosted or bare_metal") orelse switch (target.result.os.tag) { + .freestanding, .uefi => .bare_metal, + else => .hosted, + }; const manifest = Manifest.init(b); @@ -34,6 +43,7 @@ pub fn build(b: *Build) !void { opts.addOption([]const u8, "version", manifest.version); opts.addOption([]const u8, "git_commit", git_commit orelse "dev"); opts.addOption(?[]const u8, "snapshot_path", snapshot_path); + opts.addOption(TargetClass, "target_class", target_class); const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false; const enable_asan = b.option(bool, "asan", "Enable Address Sanitizer") orelse false; @@ -45,7 +55,7 @@ pub fn build(b: *Build) !void { .target = target, .optimize = optimize, .link_libc = true, - .link_libcpp = true, + .link_libcpp = target.result.abi != .msvc, .sanitize_c = enable_csan, .sanitize_thread = enable_tsan, }); @@ -55,6 +65,19 @@ pub fn build(b: *Build) !void { try linkV8(b, mod, enable_asan, enable_tsan, prebuilt_v8_path); try linkCurl(b, mod); try linkHtml5Ever(b, mod); + if (target.result.os.tag == .windows) { + mod.linkSystemLibrary("user32", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("gdi32", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("msimg32", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("imm32", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("comdlg32", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("dwrite", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("gdiplus", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("urlmon", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("winmm", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("userenv", .{ .use_pkg_config = .no }); + mod.linkSystemLibrary("dbghelp", .{ .use_pkg_config = .no }); + } break :blk mod; }; @@ -75,7 +98,7 @@ pub fn build(b: *Build) !void { }, }), }); - b.installArtifact(exe); + installArtifactCompat(b, exe, is_msvc); const run_cmd = b.addRunArtifact(exe); if (b.args) |args| { @@ -99,7 +122,7 @@ pub fn build(b: *Build) !void { }, }), }); - b.installArtifact(exe); + installArtifactCompat(b, exe, is_msvc); const run_cmd = b.addRunArtifact(exe); if (b.args) |args| { @@ -137,7 +160,7 @@ pub fn build(b: *Build) !void { }, }), }); - b.installArtifact(exe); + installArtifactCompat(b, exe, is_msvc); const run_cmd = b.addRunArtifact(exe); if (b.args) |args| { @@ -146,6 +169,124 @@ pub fn build(b: *Build) !void { const run_step = b.step("legacy_test", "Run the app"); run_step.dependOn(&run_cmd.step); } + + if (target_class == .bare_metal and target.result.os.tag == .windows) { + const release_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "scripts/windows/package_bare_metal_image.ps1", + "-PackageRoot", + "tmp-browser-smoke/bare-metal-release/image", + "-RunSmoke", + "-Url", + "https://example.com/", + }); + release_cmd.step.dependOn(b.getInstallStep()); + + const release_policy_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-policy-probe.ps1", + }); + release_policy_cmd.step.dependOn(b.getInstallStep()); + release_policy_cmd.step.dependOn(&release_cmd.step); + + const release_download_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-download-probe.ps1", + }); + release_download_cmd.step.dependOn(b.getInstallStep()); + release_download_cmd.step.dependOn(&release_policy_cmd.step); + + const release_shell_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-start-shell-probe.ps1", + }); + release_shell_cmd.step.dependOn(b.getInstallStep()); + release_shell_cmd.step.dependOn(&release_download_cmd.step); + + const release_tabs_restore_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-tabs-session-restore-probe.ps1", + }); + release_tabs_restore_cmd.step.dependOn(b.getInstallStep()); + release_tabs_restore_cmd.step.dependOn(&release_shell_cmd.step); + + const release_persistence_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-persistence-probe.ps1", + }); + release_persistence_cmd.step.dependOn(b.getInstallStep()); + release_persistence_cmd.step.dependOn(&release_tabs_restore_cmd.step); + + const release_cookie_persistence_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-cookie-persistence-probe.ps1", + }); + release_cookie_persistence_cmd.step.dependOn(b.getInstallStep()); + release_cookie_persistence_cmd.step.dependOn(&release_persistence_cmd.step); + + const release_localstorage_persistence_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-localstorage-persistence-probe.ps1", + }); + release_localstorage_persistence_cmd.step.dependOn(b.getInstallStep()); + release_localstorage_persistence_cmd.step.dependOn(&release_cookie_persistence_cmd.step); + + const release_indexeddb_persistence_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "tmp-browser-smoke/bare-metal-release/chrome-bare-metal-indexeddb-persistence-probe.ps1", + }); + release_indexeddb_persistence_cmd.step.dependOn(b.getInstallStep()); + release_indexeddb_persistence_cmd.step.dependOn(&release_localstorage_persistence_cmd.step); + + const release_step = b.step("bare_metal_release", "Package and smoke the bare-metal launch bundle"); + release_step.dependOn(&release_indexeddb_persistence_cmd.step); + } +} + +fn installArtifactCompat(b: *Build, artifact: *Build.Step.Compile, is_msvc: bool) void { + if (is_msvc) { + b.getInstallStep().dependOn(&b.addInstallArtifact(artifact, .{ + .pdb_dir = .disabled, + }).step); + return; + } + b.installArtifact(artifact); } fn linkV8( @@ -164,7 +305,7 @@ fn linkV8( .is_tsan = is_tsan, .inspector_subtype = false, .v8_enable_sandbox = is_tsan, - .cache_root = b.pathFromRoot(".lp-cache"), + .cache_root = b.pathFromRoot(if (target.result.os.tag == .windows) ".lp-cache-win" else ".lp-cache"), .prebuilt_v8_path = prebuilt_v8_path, }); mod.addImport("v8", dep.module("v8")); @@ -172,6 +313,8 @@ fn linkV8( fn linkHtml5Ever(b: *Build, mod: *Build.Module) !void { const is_debug = if (mod.optimize.? == .Debug) true else false; + const html5ever_lib_name = if (mod.resolved_target.?.result.os.tag == .windows) "litefetch_html5ever.lib" else "liblitefetch_html5ever.a"; + const is_windows_msvc = mod.resolved_target.?.result.os.tag == .windows and mod.resolved_target.?.result.abi == .msvc; const exec_cargo = b.addSystemCommand(&.{ "cargo", "build", @@ -185,7 +328,23 @@ fn linkHtml5Ever(b: *Build, mod: *Build.Module) !void { const html5ever_step = b.step("html5ever", "Install html5ever dependency (requires cargo)"); html5ever_step.dependOn(&exec_cargo.step); - const obj = out_dir.path(b, if (is_debug) "debug" else "release").path(b, "liblitefetch_html5ever.a"); + const obj = out_dir.path(b, if (is_debug) "debug" else "release").path(b, html5ever_lib_name); + if (is_windows_msvc) { + const strip_cmd = b.addSystemCommand(&.{ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "& { param([string]$src, [string]$dst) Copy-Item -Force $src $dst; $libExe='C:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\MSVC\\14.50.35717\\bin\\Hostx64\\x64\\lib.exe'; if(-not (Test-Path $libExe)){ throw 'lib.exe not found at expected VS path' }; $members=& $libExe /nologo /list $dst; $remove=$members | Where-Object { $_ -match 'compiler_builtins' }; if($remove.Count -eq 0){ exit 0 }; $tmpA=\"$dst.tmpA.lib\"; $tmpB=\"$dst.tmpB.lib\"; Copy-Item -Force $dst $tmpA; foreach($m in $remove){ & $libExe /nologo \"/remove:$m\" \"/out:$tmpB\" $tmpA | Out-Null; Move-Item -Force $tmpB $tmpA }; Move-Item -Force $tmpA $dst }", + }); + strip_cmd.addFileArg(obj); + const stripped_obj = strip_cmd.addOutputFileArg("litefetch_html5ever_stripped.lib"); + strip_cmd.step.dependOn(&exec_cargo.step); + html5ever_step.dependOn(&strip_cmd.step); + mod.addObjectFile(stripped_obj); + return; + } mod.addObjectFile(obj); } @@ -220,6 +379,8 @@ fn linkCurl(b: *Build, mod: *Build.Module) !void { fn buildZlib(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Build.Step.Compile { const dep = b.dependency("zlib", .{}); + const is_windows = target.result.os.tag == .windows; + const is_msvc = target.result.abi == .msvc; const mod = b.createModule(.{ .target = target, @@ -231,12 +392,19 @@ fn buildZlib(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.Opti lib.installHeadersDirectory(dep.path(""), "", .{}); lib.addCSourceFiles(.{ .root = dep.path(""), - .flags = &.{ - "-DHAVE_SYS_TYPES_H", - "-DHAVE_STDINT_H", - "-DHAVE_STDDEF_H", - "-DHAVE_UNISTD_H", - }, + .flags = if (is_windows and is_msvc) + &.{ + "-DHAVE_SYS_TYPES_H", + "-DHAVE_STDINT_H", + "-DHAVE_STDDEF_H", + } + else + &.{ + "-DHAVE_SYS_TYPES_H", + "-DHAVE_STDINT_H", + "-DHAVE_STDDEF_H", + "-DHAVE_UNISTD_H", + }, .files = &.{ "adler32.c", "compress.c", "crc32.c", "deflate.c", "gzclose.c", "gzlib.c", @@ -313,6 +481,8 @@ fn buildBoringSsl(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin fn buildNghttp2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Build.Step.Compile { const dep = b.dependency("nghttp2", .{}); + const is_windows = target.result.os.tag == .windows; + const is_msvc = target.result.abi == .msvc; const mod = b.createModule(.{ .target = target, @@ -336,12 +506,26 @@ fn buildNghttp2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.O lib.installHeadersDirectory(dep.path("lib/includes/nghttp2"), "nghttp2", .{}); lib.addCSourceFiles(.{ .root = dep.path("lib"), - .flags = &.{ - "-DNGHTTP2_STATICLIB", - "-DHAVE_TIME_H", - "-DHAVE_ARPA_INET_H", - "-DHAVE_NETINET_IN_H", - }, + .flags = if (is_windows and is_msvc) + &.{ + "-DNGHTTP2_STATICLIB", + "-DHAVE_TIME_H", + "-DWIN32", + "-Dssize_t=ptrdiff_t", + } + else if (is_windows) + &.{ + "-DNGHTTP2_STATICLIB", + "-DHAVE_TIME_H", + "-DWIN32", + } + else + &.{ + "-DNGHTTP2_STATICLIB", + "-DHAVE_TIME_H", + "-DHAVE_ARPA_INET_H", + "-DHAVE_NETINET_IN_H", + }, .files = &.{ "sfparse.c", "nghttp2_alpn.c", "nghttp2_buf.c", "nghttp2_callbacks.c", "nghttp2_debug.c", "nghttp2_extpri.c", @@ -354,6 +538,9 @@ fn buildNghttp2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.O "nghttp2_ratelim.c", "nghttp2_time.c", }, }); + if (is_windows) { + lib.root_module.linkSystemLibrary("ws2_32", .{ .use_pkg_config = .no }); + } return lib; } @@ -377,6 +564,7 @@ fn buildCurl( const abi = target.result.abi; const is_gnu = abi.isGnu(); + const is_msvc = abi == .msvc; const is_ios = os == .ios; const is_android = abi.isAndroid(); const is_linux = os == .linux; @@ -427,7 +615,7 @@ fn buildCurl( .CURL_DISABLE_TELNET = true, .CURL_DISABLE_TFTP = true, - .ssize_t = null, + .ssize_t = if (is_windows and is_msvc) "ptrdiff_t" else null, ._FILE_OFFSET_BITS = 64, .USE_IPV6 = true, @@ -443,18 +631,25 @@ fn buildCurl( .SIZEOF_OFF_T_CODE = byte_size(b, target, "OFF_T", .longlong), .SIZEOF_CURL_OFF_T_CODE = byte_size(b, target, "CURL_OFF_T", .longlong), - .SIZEOF_CURL_SOCKET_T_CODE = byte_size(b, target, "CURL_SOCKET_T", .int), + .SIZEOF_CURL_SOCKET_T_CODE = if (is_windows) + std.fmt.allocPrint( + b.allocator, + "#define SIZEOF_CURL_SOCKET_T {d}", + .{target.result.ptrBitWidth() / 8}, + ) catch @panic("OOM") + else + byte_size(b, target, "CURL_SOCKET_T", .int), .SIZEOF_SIZE_T_CODE = byte_size(b, target, "SIZE_T", .longlong), .SIZEOF_TIME_T_CODE = byte_size(b, target, "TIME_T", .longlong), // headers availability .HAVE_ARPA_INET_H = !is_windows, - .HAVE_DIRENT_H = true, + .HAVE_DIRENT_H = !is_windows, .HAVE_FCNTL_H = true, .HAVE_IFADDRS_H = !is_windows, .HAVE_IO_H = is_windows, - .HAVE_LIBGEN_H = true, + .HAVE_LIBGEN_H = !is_windows, .HAVE_LINUX_TCP_H = is_linux and is_gnu, .HAVE_LOCALE_H = true, .HAVE_NETDB_H = !is_windows, @@ -469,12 +664,12 @@ fn buildCurl( .HAVE_STDBOOL_H = true, .HAVE_STDDEF_H = true, .HAVE_STDINT_H = true, - .HAVE_STRINGS_H = true, + .HAVE_STRINGS_H = !is_windows, .HAVE_STROPTS_H = false, .HAVE_SYS_EVENTFD_H = is_linux or is_freebsd or is_netbsd, .HAVE_SYS_FILIO_H = !is_linux and !is_windows, .HAVE_SYS_IOCTL_H = !is_windows, - .HAVE_SYS_PARAM_H = true, + .HAVE_SYS_PARAM_H = !is_windows, .HAVE_SYS_POLL_H = !is_windows, .HAVE_SYS_RESOURCE_H = !is_windows, .HAVE_SYS_SELECT_H = !is_windows, @@ -484,7 +679,7 @@ fn buildCurl( .HAVE_SYS_UTIME_H = is_windows, .HAVE_TERMIOS_H = !is_windows, .HAVE_TERMIO_H = is_linux, - .HAVE_UNISTD_H = true, + .HAVE_UNISTD_H = !is_windows, .HAVE_UTIME_H = true, .STDC_HEADERS = true, @@ -530,6 +725,7 @@ fn buildCurl( .HAVE_WRITABLE_ARGV = !is_windows, .HAVE__SETMODE = is_windows, .USE_THREADS_POSIX = !is_windows, + .USE_THREADS_WIN32 = is_windows, // filesystem, network .HAVE_ACCEPT4 = is_linux or is_freebsd or is_netbsd or is_openbsd, @@ -547,7 +743,7 @@ fn buildCurl( .HAVE_FSETXATTR_6 = is_darwin, .HAVE_FTRUNCATE = true, .HAVE_GETADDRINFO = true, - .HAVE_GETADDRINFO_THREADSAFE = is_linux or is_freebsd or is_netbsd, + .HAVE_GETADDRINFO_THREADSAFE = is_windows or is_linux or is_freebsd or is_netbsd, .HAVE_GETHOSTBYNAME_R = is_linux or is_freebsd, .HAVE_GETHOSTBYNAME_R_3 = false, .HAVE_GETHOSTBYNAME_R_3_REENTRANT = false, @@ -609,12 +805,22 @@ fn buildCurl( lib.installHeadersDirectory(dep.path("include/curl"), "curl", .{}); lib.addCSourceFiles(.{ .root = dep.path("lib"), - .flags = &.{ - "-D_GNU_SOURCE", - "-DHAVE_CONFIG_H", - "-DCURL_STATICLIB", - "-DBUILDING_LIBCURL", - }, + .flags = if (is_windows and is_msvc) + &.{ + "-DHAVE_CONFIG_H", + "-DCURL_STATICLIB", + "-DNGHTTP2_STATICLIB", + "-DBUILDING_LIBCURL", + "-Dssize_t=ptrdiff_t", + } + else + &.{ + "-D_GNU_SOURCE", + "-DHAVE_CONFIG_H", + "-DCURL_STATICLIB", + "-DNGHTTP2_STATICLIB", + "-DBUILDING_LIBCURL", + }, .files = &.{ // You can include all files from lib, libcurl uses #ifdef-guards to exclude code for disabled functions "altsvc.c", "amigaos.c", "asyn-ares.c", diff --git a/build.zig.zon b/build.zig.zon index b7525c77c..1183866dd 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,11 +5,8 @@ .minimum_zig_version = "0.15.2", .dependencies = .{ .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.1.tar.gz", - .hash = "v8-0.0.0-xddH64J7BAC81mkf6G9RbEJxS-W3TIRl5iFnShwbqCqy", - + .path = "../zig-v8-fork", }, - //.v8 = .{ .path = "../zig-v8-fork" }, .brotli = .{ // v1.2.0 .url = "https://github.com/google/brotli/archive/028fb5a23661f123017c060daa546b55cf4bde29.tar.gz", @@ -24,8 +21,7 @@ .hash = "N-V-__8AAL15vQCI63ZL6Zaz5hJg6JTEgYXGbLnMFSnf7FT3", }, .@"boringssl-zig" = .{ - .url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096", - .hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK", + .path = "../boringssl-zig", }, .curl = .{ .url = "https://github.com/curl/curl/releases/download/curl-8_18_0/curl-8.18.0.tar.gz", diff --git a/docs/FULL_BROWSER_MASTER_TRACKER.md b/docs/FULL_BROWSER_MASTER_TRACKER.md new file mode 100644 index 000000000..aa674c67d --- /dev/null +++ b/docs/FULL_BROWSER_MASTER_TRACKER.md @@ -0,0 +1,969 @@ +# Full Browser Master Tracker + +This tracker defines the path from the current experimental Lightpanda fork to +a production-ready minimalist Zig browser for real daily use. The target is not +full Chrome parity in every area. The target is a fast, installable, +user-facing browser that can browse the modern web reliably and ship most core +features users expect from Chrome-class browsing. + +## Product Target + +The production bar for this fork is: + +- open and use most common real-world sites without falling back to CDP only +- provide a stable headed browser UX with tabs, address bar, history, reload, + stop, downloads, settings, and session persistence +- render modern HTML/CSS/JS content with acceptable fidelity for mainstream + browsing +- support core Chrome-like daily-use features: multi-tab browsing, persistent + profile, cookies/storage, TLS, uploads/downloads, autofill-adjacent form + usability, screenshots, clipboard, find-in-page, and crash recovery +- preserve headless and CDP strengths as secondary product modes, not the main + product definition + +## Non-Goals For First Production Cut + +These are explicitly out of scope for the first production-ready release unless +they become necessary for site compatibility: + +- Chrome extension ecosystem compatibility +- Google account sync or cloud profile sync +- full Chrome DevTools parity +- multi-process sandbox parity with Chrome + +## Current Baseline + +The fork already has a real headed Windows foundation: + +- Windows/MSVC build is stable +- `browse` runs in a native Win32 window +- address bar navigation works +- back, forward, reload, and stop browser chrome works +- stop restores the last committed live page/context +- wrapped-link click navigation works +- screenshots/export are wired through the headed presentation surface +- raster `` rendering works for common sources used by the probes +- focus/autofocus/input bootstrap is working in headed mode +- label activation and `Enter` form submit basics work +- bounded localhost smoke probes exist for navigation, history, reload, stop, + wrapped links, and form interactions +- native tab strip, reopen-closed-tab, and session restore are working in the + headed shell +- native overlays exist for history, bookmarks, downloads, and basic settings +- headed `browse` now has zoom controls, find-in-page, bookmark persistence, + download persistence, homepage navigation, and persisted default zoom / + restore-session settings +- headed `browse` now has a persisted script-popup policy with Win32 settings + UI, blocked-runtime coverage, and allowed/blocked script popup acceptance +- internal `browser://history`, `browser://bookmarks`, `browser://downloads`, + `browser://settings`, and `browser://tabs` pages now support stateful + actions, not just static snapshots +- those internal history, bookmark, and download pages now keep per-tab sort + state, expose in-page sort controls, and refresh titles/counts plus row order + live as the sort mode changes +- the normal headed shell shortcuts now target internal browser pages first, + while the legacy overlays are secondary diagnostic surfaces +- the headed painter now keeps direct paragraph text in the same inline flow as + inline child chips and links for the current simple mixed-inline path, + instead of splitting the paragraph into a separate text band above the inline + controls +- invalid or unsupported selector syntax in page JS and stylesheet matching no + longer tears down headed mode; selector syntax errors now stay in-page as JS + failures while invalid stylesheet selectors are skipped safely +- headed JS microtask checkpoints now run inside the target context with a real + V8 handle scope, removing the clean Google startup `HandleScope::CreateHandle` + fatal seen during Promise-heavy page initialization repros +- the first real CSS/layout compatibility slice is in place for headed + documents: `min(...)`, `max(...)`, `clamp(...)`, `%`, `vw`, and `vh` lengths; + block auto-margin centering; flex-column centering; centered inline child + flow for `text-align:center`; and absolute out-of-flow positioning, with + focused painter tests plus bounded headed runtime probes for microtask + containment, centered flex hero layout, and absolute corner docking +- the headed painter now also respects basic text styling on real labels and + inline text, including `line-height`, `letter-spacing`, `word-spacing`, and + `text-transform`, with Google nav-style and dedicated text-style regression + coverage wired into the render tests +- the headed painter now also threads inherited CSS `opacity` through the + display-list path so rect, text, image, and canvas paint commands carry a + consistent alpha multiplier into headed composition +- overflow:auto containers now keep scrolled link regions visible enough for + real click activation on the headed surface, and screenshot readiness no + longer burns the capture on bodyless-but-substantive or dense text-only + frames; bounded scroll, visible-link, opacity, and text-style probes now + cover the programmatic scroll state and the resulting navigation path +- CSS translate transforms now carry through headed painting and DOM + hit-testing for translated controls and links, with bounded screenshot and + interaction coverage proving the transformed button and link geometry both + render and remain targetable +- the headed painter now also paints a pragmatic one-layer `box-shadow` for + boxes and controls, with a renderer test plus a bounded headed screenshot + probe proving the visible offset shadow on the real Win32 surface +- flex row layout now also respects authored `order`, `flex-shrink`, + `align-content`, and `align-self`, with bounded headed probes proving + ordered links, shrunk boxes, wrapped line spacing, and per-item vertical + alignment on the real Win32 surface +- the headed painter now caches painted element layout boxes so DOM + `getBoundingClientRect`/hit-testing can reuse painted geometry for visible + elements instead of falling back to the older sibling-position heuristic +- flex column layout now also respects authored `flex-grow`, `flex-shrink`, + `justify-content`, and `column-reverse`, with renderer tests plus bounded + headed probes proving the grow, justify, reverse, and stretch paths on the + real Win32 surface +- the browser now accepts an explicit profile directory override and resolves + its persistence root through a small host-path abstraction, which is the + first cross-platform/bare-metal support seam for cookies, storage, + downloads, and telemetry IDs + +## Achieved Gates + +### Gate A: Windows Headed Foundation + +Status: Achieved + +- native headed window lifecycle +- native input translation and text editing baseline +- `browse` command path +- Windows runtime stabilization and smoke probes + +### Gate B: Headed Browser Interaction MVP + +Status: Achieved + +- address bar navigation +- back/forward/reload/stop chrome +- wrapped-link hit testing and navigation +- live page restore after stop + +### Gate C: Shared Presentation Surface + +Status: Achieved + +- display-list based headed presentation path +- screenshot/export on the same presentation path +- basic text, box, link, and image presentation + +### Gate D: Basic Form/Input Reliability + +Status: Achieved + +- autofocus and initial typing +- label activation +- `Enter` submit path +- stable Windows `SendInput`-driven headed probes + +## Remaining Gates + +### Gate 1: Browser Shell MVP + +Status: Active next milestone + +Goal: +- turn the current single-page headed shell into a minimal real browser shell + +Exit criteria: +- tab strip with open, close, switch, duplicate, and reopen closed tab +- new-window and basic popup/window handling policy +- visible loading/error states and disabled chrome state where applicable +- history UI, bookmarks UI, downloads UI, and basic settings UI +- find-in-page and zoom controls + +Current state inside Gate 1: +- tabs now cover open, close, switch, duplicate, reopen, and clean session restore +- history, bookmarks, downloads, settings, find, and zoom are present in the + headed shell +- disabled close-state for the single remaining tab is implemented +- rendered `_blank` anchor popups now open in a new tab through the native + headed surface +- form-driven `_blank` submission now reaches a stable headed new-tab flow +- script-driven `window.open()` now reaches stable headed `_blank` and + named-target tab flows, and later launcher-page callbacks remain alive after + popup activation +- script popup policy now covers allowed vs blocked `window.open()` behavior, + with persisted settings and headed Win32 shell controls +- browser-side named-target queueing/reuse is now implemented for anchors and + form submission, with direct page/session tests covering anchor click, anchor + `Enter`, and GET/POST form submission +- bounded headed probes remain the acceptance gate for `_blank` popup flows, + rendered named-target anchor pointer activation, script popup tab reuse, and + launcher-background callback survival after popup open +- bounded headed probes now also cover popup policy persistence through the + settings overlay and blocked script-popup runtime behavior +- rendered same-tab link activation now dispatches a real DOM click first, so + `onclick`, `preventDefault`, and click-time href mutation are preserved on the + headed surface before any direct navigation fallback +- dedicated internal browser pages now exist for history, bookmarks, downloads, + settings, and tabs, backed by current session state or the persisted stores + already in place +- those browser pages are reachable through both native headed shortcuts and + `browser://start`, `browser://tabs`, `browser://history`, + `browser://bookmarks`, `browser://downloads`, and `browser://settings` + address-bar aliases +- those browser pages now execute real internal actions: + - tab new, activate, duplicate, reload, close, and reopen-closed flows + through `browser://tabs/...` + - history traverse, reload-safe reopen, and clear-session collapse + - bookmark add-current, open, and remove backed by the persisted bookmark + store + - download source, remove, and clear-inactive backed by the persisted + download store + - settings toggles for restore-session, script popups, default zoom, and + homepage mutation + - homepage navigation to an internal page plus restart-time restore of the + internal page in the session model +- the standard shell shortcuts now open those internal pages directly: + - `Ctrl+Shift+A` tabs + - `Ctrl+H` history + - `Ctrl+Shift+B` bookmarks + - `Ctrl+J` downloads + - `Ctrl+,` settings +- the internal pages now include a shared shell header/nav plus a + `browser://start` hub page, and `Alt+Home` falls back to that start page when + no homepage is configured +- bounded headed browser-page probes now cover: + - history, bookmarks, downloads, and settings actions + - bookmark add-current, history clear-session, and download clear-all flows + - start-page cross-navigation + - `browser://start` quick actions and settings-summary mutations through + in-page document actions, not only address-bar routes + - `browser://start` recent history/bookmark/download preview actions through + in-page document actions + - tabs-page tab-management actions and reload/reopen recovery + - `browser://tabs` indexed closed-tab reopen through in-page document actions + - homepage-to-internal-page restart restore +- legacy overlays are still available for diagnostics on secondary shortcuts, + but they are no longer the primary shell path +- internal page titles now stay user-facing across active presentation, + background tab state, restart restore, and the zero-count downloads case +- headed navigation failures now promote into a structured `browser://error` + page instead of a raw placeholder document +- that error state now remains visible across `browser://start` and + `browser://tabs`, with bounded headed probes for invalid-address handling, + disabled back/forward chrome on error, error-state preservation, and + recovery once the target becomes reachable again +- `browser://history`, `browser://bookmarks`, and `browser://downloads` now + keep live per-tab filter state, support internal `filter/...` and + `filter-clear` routes, expose quick-filter links directly on the page, and + have bounded headed probes for quick-filter plus clear-filter document + actions +- those same internal history/bookmark/download pages now expose explicit + per-row open-in-new-tab actions, with bounded headed probes proving the new + tab opens while the originating internal page tab remains intact +- those same internal history/bookmark/download pages now also keep per-tab + sort state, support internal `sort/...` routes, expose in-page sort controls, + and have bounded headed probes for sort changes plus sorted row actions +- `browser://history` now also supports in-page single-entry removal plus + safe `remove-before` / `remove-after` pruning that preserves the current live + page, with bounded headed probes for single-remove and both prune directions +- `browser://bookmarks` now supports persisted in-page reorder actions in saved + order mode, and `browser://downloads` now supports in-place retry of failed + and interrupted entries with bounded headed document-action coverage +- `browser://bookmarks` now also supports opening all currently visible + bookmark rows in new background tabs based on the page's active filter and + sort state, with a bounded headed probe proving the filtered bookmarks page + stays active while the visible bookmark targets open in saved-order +- `browser://downloads` now also supports native shell actions for completed + entries, including `Open file`, `Reveal file`, and `Open downloads folder`, + with bounded headed probes proving each action fires while the originating + downloads page remains active +- headed `browse` tabs now share one persistent cookie jar instead of keeping + cookie state session-local per tab, and that cookie jar now survives browser + restart and can be cleared from `browser://settings`, with bounded same-tab, + cross-tab, restart, and clear-cookies headed probes +- headed `browse` tabs now also share one persistent origin-scoped + `localStorage` shed across tabs and browser restart, and that storage can be + cleared from `browser://settings`, with bounded cross-tab, restart, and + clear-local-storage headed probes +- that same headed `localStorage` path now also dispatches real cross-tab + `storage` events through `window.onstorage` and `StorageEvent`, with a + bounded headed probe proving a listener tab receives the event after a + sibling tab mutates `localStorage` and both tabs remain alive afterward +- headed `browse` tabs now also keep real per-tab `sessionStorage` state that + survives same-tab navigation but does not leak across tabs or browser + restart, with bounded same-tab, cross-tab, and restart headed probes +- headed `browse` tabs now also share one persistent origin-scoped IndexedDB + shed across tabs and browser restart, and that storage can be cleared from + `browser://settings`, with bounded cross-tab, restart, and clear-IndexedDB + headed probes +- that same headed IndexedDB path now also keeps basic object-store index + definitions and indexed lookups persistent across tabs and browser restart, + with focused DOM tests plus a bounded headed probe proving indexed entries + survive restart and can still be read back by index name and key +- that same headed IndexedDB path now also supports object-store and index + cursor iteration, with focused DOM tests plus a bounded headed cross-tab + probe proving seeded cursor rows can be read back in sorted order from a + sibling tab through both `objectStore.openCursor()` and `index.openCursor()` +- that same headed IndexedDB path now also exposes real transaction `mode` + state on the JS surface for `readonly` vs `readwrite` single-store + transactions, with focused DOM coverage plus a bounded headed probe proving + page JS can observe the expected mode values before a successful write +- headed `fetch(...)` now honors credentials policy correctly on authenticated + pages, with bounded localhost probes proving: + - default same-origin fetch keeps cookie plus inherited auth + - `credentials: 'omit'` suppresses both cookie and auth + - cross-origin `same-origin` suppresses credentials + - cross-origin `include` sends cookies but not inherited auth +- headed `Request` and `fetch(...)` now also honor `AbortSignal`, with focused + DOM tests proving `Request.signal` cloning plus immediate-abort rejection, + and a bounded headed probe proving an in-flight slow fetch aborts at runtime + with `AbortError` while the server observes the connection being cut +- root `Content-Disposition: attachment` navigations now promote into the + headed download manager instead of degrading into navigation errors: + address-bar navigations, in-page link activations, and direct startup URLs + all enqueue real downloads, restore the suspended page when one exists, and + fall back to `browser://downloads` when there is no live page to restore +- those same root attachment navigations now adopt the original response stream + directly into the headed download manager instead of aborting and issuing a + second GET, with bounded headed probes proving a single request for + address-bar, in-page link, and direct-startup attachment flows +- headed Windows `browse` now has a native file chooser path for rendered file + inputs, including multi-select file inputs, plus real multipart form + submission with selected files and bounded headed probes for single-file + select-submit, cancel, replace, and multi-file submit flows +- that same Win32 chooser path now derives native dialog file filters from + common `accept` hints (extensions plus common MIME and wildcard families) + instead of ignoring `accept` entirely, with focused helper coverage and full + headed upload regression sweeps +- those same headed upload flows now compose cleanly with named popup targets + and attachment responses: bounded probes cover target-tab multipart upload, + same-context upload-to-attachment with restored source page plus downloads + page visibility, and target-tab upload-to-attachment with both managed + download capture and source-tab preservation +- headed network image requests in the Win32 renderer now use the shared + browser `Http` runtime when available instead of the old URLMon-only path, + with a bounded localhost probe proving the image request carries + `User-Agent: Lightpanda/1.0` and renders successfully on the headed surface +- those same headed network image requests now inherit page/session request + policy for cookies and referer, with a bounded localhost probe proving the + image request carries both the page cookie and the active page referer while + still rendering successfully on the headed surface +- those same headed network image requests now also carry redirect-set cookies + through the shared `Http` runtime path, with a bounded localhost redirect + probe proving the final image request sends both the original page cookie and + the cookie set on the 302 hop before the image is rendered on the headed + surface +- those same headed network image requests now distinguish credentialed and + anonymous fetch policy, with bounded localhost probes proving a credentialed + auth image still carries page cookie, referer, and URL-userinfo Basic + `Authorization`, while `crossorigin="anonymous"` suppresses both cookie and + auth header and still renders successfully on the headed surface +- those same headed network image requests now identify themselves more like + real image subresources instead of generic fetches, with a bounded localhost + probe proving the shared-runtime request carries an explicit image `Accept` + header while still rendering successfully on the headed surface +- same-origin protected subresources now inherit page-URL Basic auth on the + shared request-policy path without leaking URL userinfo through `Referer`, + with bounded localhost probes proving a relative headed image request and an + external script request both carry inherited auth, sanitized referer, + cookies, and that the authorized script actually executes afterward +- those same connected external scripts now also distinguish credentialed vs + anonymous fetch policy, with bounded localhost probes proving a credentialed + script still carries cookie, sanitized referer, inherited auth, and executes + successfully, while `crossorigin="anonymous"` suppresses both cookie and + auth on the script request itself and still executes successfully on the + headed surface +- connected external module scripts now ride the same shared request-policy + path for both root and child imports, with bounded localhost probes proving + credentialed and anonymous static module graphs carry the correct + cookie/referer/auth policy on both the root request and the child request, + and that the module graph executes successfully on the headed surface +- connected `link rel=stylesheet` elements now load through the shared browser + `Http` runtime path, expose `link.sheet`, participate in + `document.styleSheets`, and carry page cookie, sanitized referer, inherited + auth, and an explicit stylesheet `Accept` header on protected same-origin + loads, with a bounded headed localhost probe proving the stylesheet request + succeeds and the page observes both `link.sheet` and stylesheet load + completion +- those same connected external stylesheets now also distinguish credentialed + vs anonymous fetch policy, with a bounded headed localhost probe proving + `crossorigin="anonymous"` suppresses both cookie and auth while preserving + sanitized referer, stylesheet `Accept`, successful stylesheet load, and + computed-style application on the headed surface +- those same connected internal and external stylesheets now populate + `cssRules` and feed the current `getComputedStyle` / headed painter path for + simple authored rules, with bounded tests and a headed localhost probe + proving a protected external stylesheet changes the computed page background + instead of only firing `load` +- those same connected external stylesheet `@import` graphs now carry the + correct protected vs anonymous request policy on both the root stylesheet + request and the imported child stylesheet request, with bounded headed + localhost probes proving imported styles apply successfully in both modes +- simple `@font-face` parsing and shared-runtime font fetches now ride that + same stylesheet-driven path, with `document.fonts` exposing loaded faces by + `size`, `status`, `check(...)`, and `load(...)`, and bounded headed + localhost probes proving protected and anonymous font requests carry the + correct cookie, sanitized referer, auth suppression or inheritance, explicit + font `Accept`, and loaded page state on the headed surface +- headed Win32 text rendering now carries authored `font-family`, + `font-weight`, and `font-style` through the display list into real GDI font + selection for installed fonts, with a bounded screenshot probe proving the + headed surface produces materially different glyph widths for authored font + runs instead of always falling back to one generic face +- those same stylesheet-backed `@font-face` entries now retain supported TTF + and OTF bytes, flow through the shared display-list presentation path, and + register as private Win32 fonts for headed text rendering, with a bounded + two-page localhost screenshot probe proving the same authored family renders + with materially different glyph widths when the private font is present vs + when the font URL is missing +- those same private stylesheet-backed font flows now parse multi-source + `src:` lists with format hints and prefer a later renderable TTF/OTF + fallback over an earlier unsupported WOFF/WOFF2 source when present, with a + bounded headed screenshot probe proving a later truetype fallback still + affects the surface after an earlier missing `woff2` source +- on-screen and offscreen canvas 2D contexts now keep real RGBA backing + stores for `fillRect`, `clearRect`, `strokeRect`, `getImageData`, and + `putImageData`, and headed Win32 `browse` now renders those canvas pixels on + the shared display-list path with a bounded screenshot probe proving the + rendered border, composited fill, and cleared interior +- those same on-screen and offscreen canvas 2D contexts now also keep real + text state plus Win32-backed `fillText(...)` and `strokeText(...)`, with + focused DOM tests and a bounded headed screenshot probe proving red filled + and blue stroked glyph pixels reach the real destination canvas surface +- those same canvas text paths now also expose real `measureText(...)` + `TextMetrics` objects backed by the same Win32 measurement path, with + focused DOM tests plus a bounded headed screenshot probe proving JS-sized + red and blue bars differ on the real surface when authored fonts differ +- those same on-screen and offscreen canvas 2D contexts now also support a + first real `drawImage(...)` slice for `HTMLCanvasElement` and + `OffscreenCanvas` sources, including direct copy, simple scaling, and + source-rect cropping, with focused DOM tests plus a bounded headed Win32 + screenshot probe proving copied red, blue, and green source pixels reach the + real destination canvas surface +- those same on-screen and offscreen canvas 2D contexts now also support + `drawImage(HTMLImageElement, ...)`, with focused DOM tests plus a bounded + headed Win32 screenshot probe proving decoded red and cropped blue image + pixels reach the real destination canvas surface +- those same canvas 2D contexts now also support a first real path slice for + `beginPath`, `moveTo`, `lineTo`, `rect`, `fill`, and `stroke`, with focused + DOM tests plus a bounded headed Win32 screenshot probe proving filled green + regions and blue stroke segments reach the real destination canvas surface +- the headed Win32 canvas path now also includes a first real `webgl` + rendering-context slice for `clearColor(...)` plus `clear(COLOR_BUFFER_BIT)`, + with focused DOM tests plus a bounded headed Win32 screenshot probe proving a + full `120x80` clear-colored canvas region reaches the real destination + surface +- that same headed Win32 `webgl` slice now also has a bounded runtime quality + gate for `drawingBufferWidth` / `drawingBufferHeight`, proving resized WebGL + buffer dimensions remain visible to page JS while a clear-colored surface + still reaches the headed screenshot path +- that same headed Win32 `webgl` path now also includes a first real + shader/program/buffer draw slice for `createShader`, `shaderSource`, + `compileShader`, `createProgram`, `attachShader`, `linkProgram`, + `createBuffer`, `bufferData`, `vertexAttribPointer`, and + `drawArrays(TRIANGLES, ...)`, with focused DOM tests plus a bounded headed + screenshot probe proving a red triangle reaches the real destination canvas + surface +- that same headed Win32 `webgl` path now also supports a first indexed-draw + and uniform-color slice for `getUniformLocation`, `uniform4f`, + `ELEMENT_ARRAY_BUFFER`, and `drawElements(TRIANGLES, ..., UNSIGNED_SHORT, ...)`, + with bounded headed screenshot coverage proving a uniform-colored indexed + triangle reaches the real destination canvas surface +- that same headed Win32 `webgl` path now also supports a first varying-color + attribute slice with two enabled vertex attributes, interpolated per-vertex + color fill, and bounded headed screenshot coverage proving red, green, and + blue regions reach the real destination canvas surface from one triangle +- the headed browser runtime now also exposes a first real `WebSocket` + browser-API slice with `CONNECTING` -> `OPEN` -> `CLOSED` state transitions, + `send`, `close`, `onopen`, `onmessage`, `onerror`, and `onclose`, with a + focused localhost DOM test plus a bounded headed echo probe proving text + frames round-trip on the live headed surface path +- that same headed `WebSocket` runtime now also covers binary echo plus richer + close semantics through `binaryType`, binary `message` payloads, and + `CloseEvent` `code` / `reason` / `wasClean`, with a bounded headed localhost + probe proving binary frames round-trip and server-initiated close details + reach page JS on the live headed surface path +- that same headed `WebSocket` runtime now also covers client-requested + subprotocol negotiation plus surfaced negotiated extensions, with a bounded + headed localhost probe proving a requested protocol list yields negotiated + `protocol === "superchat"`, `extensions === "permessage-test"`, binary + echo still works, and a clean client close reaches page JS correctly +- the current headed painter now also keeps simple block paragraphs with mixed + direct text plus inline child elements on one shared inline row instead of + splitting the direct text into a separate label band above the inline chips, + with a bounded headed screenshot probe proving left-side paragraph text and + inline chips share the same content row +- that same mixed-inline painter path now also keeps narrow wrapped mixed + inline paragraphs in one shared flow across multiple rows and treats `
` + as a real line break inside that flow, with bounded headed screenshot probes + proving wrapped chips/text stay in one content flow and the following + paragraph remains below the wrapped or broken inline content +- wrapped mixed-inline anchors now also have bounded headed click coverage on a + lower wrapped fragment row, proving the lower-row link fragment still + navigates correctly after the inline-flow and wrapping changes +- that same mixed-inline interaction coverage now also includes `
`-split + inline links and longer wrapped anchors with multiple later fragments, with + bounded headed probes proving navigation still works from those later visual + fragments instead of only from the first row +- that same mixed-inline headed interaction path now also covers later-row + controls, with bounded probes proving a wrapped inline button still + activates from its lower row and a `
`-split inline text input still + focuses and accepts typed text from the later row on the headed surface +- that same later-row mixed-inline control path now also keeps keyboard + behavior after focus, with bounded probes proving a wrapped inline button + can be re-activated with `Space` and a `
`-split inline text input can + submit its form on `Enter` from the headed surface +- mixed control/link coexistence is now covered too, with bounded probes + proving a wrapped later-row button and a lower later-row link remain + independently usable in the same paragraph by both direct click and + button-focus `Tab` then `Enter` traversal +- dense mixed-inline traversal is now covered as well, with a bounded probe + proving one wrapped paragraph can hand off focus from a later-row button to + a later-row input and then to a later-row link through `Tab` progression + while each target still performs its real headed action +- that same mixed-inline later-row interaction coverage now also includes + checkbox/link coexistence, with a bounded probe proving a wrapped later-row + checkbox can be toggled by click and `Space`, and that `Tab` then `Enter` + still reaches and activates a later-row link in the same paragraph +- that same mixed-inline later-row control coverage now also includes dense + checkbox/button/link coexistence, with a bounded probe proving one wrapped + paragraph can handle later-row checkbox click activation, later-row button + click activation, and then `Tab`/`Enter` traversal into a later-row link +- that same later-row mixed-inline selection path now also includes radio/link + coexistence, with a bounded probe proving a wrapped later-row radio can be + selected by click and that `Tab` then `Enter` still reaches and activates a + later-row link in the same paragraph +- that same dense later-row mixed-inline control coverage now also includes + radio/button/link coexistence, with a bounded probe proving one wrapped + paragraph can handle later-row radio click selection, later-row button click + activation, and then `Tab`/`Enter` traversal into a later-row link +- that same dense later-row mixed-inline control coverage now also includes one + wrapped paragraph containing later-row checkbox, radio, button, and link + targets together, with a bounded probe proving click activation on the + checkbox, `Tab`+`Space` activation on the later-row radio and button, and + `Tab`+`Enter` traversal into the later-row link in DOM order +- that same dense later-row mixed-inline control coverage now also includes a + wrapped same-family radio pair plus later-row button and link, with a + bounded probe proving click activation on the first radio, `Tab`+`Space` + selection of the second radio in the same group, then `Tab`+`Space` button + activation and `Tab`+`Enter` link navigation in DOM order +- that same dense later-row mixed-inline control coverage now also includes a + wrapped same-family checkbox pair plus later-row button and link, with a + bounded probe proving click activation on the first checkbox, `Tab`+`Space` + activation on the second checkbox, then `Tab`+`Space` button activation and + `Tab`+`Enter` link navigation in DOM order +- that same later-row mixed-inline same-family checkbox coverage now also + includes a wrapped form paragraph with a later-row submit control, with a + bounded probe proving click activation on the first checkbox, `Tab`+`Space` + activation on the second checkbox, and `Tab`+`Space` submission through the + real headed form-submit path +- that same later-row mixed-inline same-family radio coverage now also + includes a wrapped form paragraph with a later-row submit control, with a + bounded probe proving click activation on the first radio, `Tab`+`Space` + selection of the second radio in the same group, and `Tab`+`Space` + submission through the real headed form-submit path +- that same later-row mixed-inline same-family checkbox coverage now also + includes a wrapped form paragraph with a later-row text input before the + submit control, with a bounded probe proving click activation on the first + checkbox, `Tab`+`Space` activation on the second checkbox, `Tab`-driven text + entry into the later-row input, and `Tab`+`Space` submission through the + real headed form-submit path +- that same later-row mixed-inline same-family radio coverage now also + includes a wrapped form paragraph with a later-row text input before the + submit control, with a bounded probe proving click activation on the first + radio, `Tab`+`Space` selection of the second radio in the same group, + `Tab`-driven text entry into the later-row input, and `Tab`+`Space` + submission through the real headed form-submit path +- that same later-row mixed-inline form coverage now also includes a wrapped + mixed-family paragraph where checkbox, radio, text input, and submit coexist + in DOM order, with a bounded probe proving click activation on the checkbox, + `Tab`+`Space` activation on the later-row radio, typed input on the later-row + text field, and `Tab`+`Space` submission through the real headed form-submit + path +- that same later-row mixed-inline form coverage now also includes a denser + wrapped mixed-family paragraph where a checkbox pair, a radio pair, text + input, and submit coexist in DOM order, with a bounded probe proving click + activation on the first checkbox, `Tab`+`Space` activation on the second + checkbox, first radio, and second radio, then typed input and real headed + form submission through the later-row submit control +- that same later-row mixed-inline form coverage now also includes a further + dense wrapped mixed-family paragraph where a checkbox pair, a radio pair, + two text inputs, and submit coexist in DOM order, with a bounded probe + proving click activation on the first checkbox, `Tab`+`Space` activation on + the second checkbox, first radio, and second radio, then typed input through + both later-row text fields before real headed form submission through the + later-row submit control +- that same later-row mixed-inline form coverage now also includes submit/link + coexistence after those dense controls, with bounded probes proving the same + wrapped paragraph can either reach a later-row link and navigate or continue + past that link to a later-row submit control and complete a real headed form + submission +- that same later-row mixed-inline form coverage now also includes two distinct + later-row link targets before a later-row submit control, with bounded + probes proving the same dense wrapped paragraph can independently reach the + first link, reach the second link, or continue past both links to the later- + row submit control and complete a real headed form submission +- that same later-row mixed-inline form coverage now also includes three + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first link, second link, or third link, or continue past all three + links to the later-row submit control and complete a real headed form + submission +- that same later-row mixed-inline form coverage now also includes four + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first, second, third, or fourth link, or continue past all four + links to the later-row submit control and complete a real headed form + submission +- that same later-row mixed-inline form coverage now also includes five + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first, second, third, fourth, or fifth link, or continue past all + five links to the later-row submit control and complete a real headed form + submission +- that same later-row mixed-inline form coverage now also includes six + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first, second, third, fourth, fifth, or sixth link, or continue + past all six links to the later-row submit control and complete a real + headed form submission +- that same later-row mixed-inline form coverage now also includes seven + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first, second, third, fourth, fifth, sixth, or seventh link, or + continue past all seven links to the later-row submit control and complete a + real headed form submission +- that same later-row mixed-inline form coverage now also includes eight + distinct later-row link targets before a later-row submit control, with + bounded probes proving the same dense wrapped paragraph can independently + reach the first, second, third, fourth, fifth, sixth, seventh, or eighth + link, or continue past all eight links to the later-row submit control and + complete a real headed form submission +- next blocker: keep turning internal pages into richer live shell surfaces so + fewer browser-shell flows still depend on address-bar routes or secondary + overlay surfaces + +### Gate 2: Shared Subresource Loader And Profile + +Status: Active + +Goal: +- move page assets and browser state onto a consistent browser-managed runtime + +Exit criteria: +- images, connected stylesheets, scripts, fonts, and other subresources use the shared browser + network/client path +- cookies, cache, auth, proxy, redirects, uploads, downloads, and persistent + profile storage behave consistently +- file chooser and download manager flows exist +- same-origin, CORS, CSP, mixed-content, and certificate error behavior are + coherent enough for mainstream browsing + +Current known gap entering Gate 2: +- explicit download requests, adopted root-attachment transfers, and other + browser-managed resource flows still do not share one unified runtime path + for transfer ownership, persistence, and policy +- headed `browse` now has one shared persistent cookie jar, origin-scoped + `localStorage` store, and origin-scoped IndexedDB store across tabs and + restart, with settings clear paths for all three, but broader persisted + profile state is still thin: cache policy and stronger profile persistence + beyond cookies/storage are still open +- headed network images now ride the shared `Http` runtime path and inherit + page/session cookies, sanitized referer, redirect-set cookies, URL-userinfo + Basic Authorization, same-origin page-URL Basic auth inheritance, anonymous + credential suppression, and an explicit image `Accept` header, but broader + auth beyond page-URL Basic credentials and richer resource-type behavior are + still open +- connected external scripts and static module imports now ride the shared + `Http` runtime path with inherited auth, sanitized referer, cookie policy, + explicit script `Accept`, and anonymous credential suppression coverage, but + broader script/resource parity and one unified subresource ownership path + are still open +- connected `link rel=stylesheet` requests now ride the same shared `Http` + runtime path with `link.sheet` / `document.styleSheets` coverage and bounded + protected-load auth/cookie/referer/`Accept` verification plus anonymous + credential suppression, stylesheet body application now exists for the + current simple authored-rule path, and imported child stylesheets now keep + the same protected vs anonymous policy as the root request; stylesheet- + backed `@font-face` fetches and `document.fonts` now ride that same path, + and headed Win32 text rendering now honors both authored installed-font + family/style/weight and private TTF/OTF plus WOFF/WOFF2 stylesheet-backed + `@font-face` rendering on the surface, including later renderable + fallbacks in multi-source `src:` lists, but broader CSS fidelity, real + text shaping, wider font-format parity beyond WOFF/WOFF2, script/font/ + resource parity, and one unified subresource ownership path are still open; + the current headed painter now also uses measured Win32 text extents + instead of pure character-count heuristics for text runs and inline/button + width decisions +- native file chooser, multi-select file inputs, and multipart upload flows + now work end to end in headed Windows `browse`, but upload transport still + needs to converge with the same broader shared runtime/policy path as other + browser-managed resources +- popup-target and attachment-response upload combinations are now runtime- + covered; the remaining work is less about basic composition and more about + converging those flows with the same broader shared transfer/runtime policy + +### Gate 3: Layout Engine Replacement + +Status: Active + +Goal: +- replace the remaining dummy and heuristic layout paths with a real layout + engine + +Exit criteria: +- block and inline formatting contexts behave predictably +- flexbox support is usable on common sites +- positioning, overflow, fixed/sticky basics, margin/padding/border handling, + and intrinsic sizing are implemented +- form controls and replaced elements layout correctly in normal documents + +Current state inside Gate 3: +- the first compatibility slice is landed for common real-site layout pressure: + safer selector failure containment, length resolution for `%`/`vw`/`vh` plus + `min(...)`/`max(...)`/`clamp(...)`, auto-margin centering, flex-column + centering, centered inline child flow, and absolute corner positioning +- that same slice now also covers a first row-direction flex path with wrap, + `justify-content` spacing, and `align-items` vertical placement for common + chip/button-style rows, plus selector compatibility for `:lang(...)`, + `:dir(...)`, `:open`, and vendor `:-webkit-any-link` / `:-moz-any-link` +- the headed painter now caches painted element layout boxes so + `getBoundingClientRect` and hit-testing can reuse painted geometry for + visible elements instead of falling back to the older sibling-position + heuristic +- flex column layout now also respects authored `flex-grow`, `flex-shrink`, + `justify-content`, and `column-reverse`, with renderer tests plus bounded + headed probes proving the grow, justify, reverse, and stretch paths on the + real Win32 surface +- headed screenshot export now waits for a real painted presentation with + positive painted height and real draw or interactive regions instead of + consuming the one-shot capture on the initial root placeholder frame, with a + bounded delayed-content probe proving async timer-driven page content reaches + the exported PNG +- selector compatibility now also covers relative `:has(...)` combinators + (`>`, `+`, `~`, and descendant default) plus safer functional pseudo parsing + across quoted strings, bracketed attribute values, nested parentheses, and + top-level comma splitting +- stylesheet rule application now also keeps valid selector-list branches when + a sibling branch is unsupported by the current engine, so common real-site + vendor or pseudo-element branches stop dropping the entire declarations block +- fixed-position viewport anchoring now survives inline-content-flow containers + instead of being re-offset into the parent content box, and the computed + style path now defaults common controls and replaced elements like buttons, + inputs, selects, textareas, images, canvas, and iframes to `inline-block` + instead of `block` +- flex row layout now also supports bounded item growth from `flex-grow` / + common `flex` shorthand handling, so header and search-bar style middle items + can expand between fixed siblings instead of staying at their intrinsic width +- the remaining Gate 3 work is now centered on broader intrinsic sizing, + overflow interaction, stronger positioned/fixed behavior, and flex/table edge + cases on mainstream sites rather than the cache bridge itself +- headed screenshot export now also refuses to capture while navigation is + still explicitly loading, with a bounded slow-image probe proving the export + waits for the real loaded image instead of the earlier pre-load placeholder +- bounded headed probes now prove: + - Promise-microtask selector failures no longer kill the headed browser + - centered hero-style flex layouts reach the real Win32 surface + - absolute left/right corner docking plus later normal flow reach the real + Win32 surface + - centered wrapped flex-row content reaches the real Win32 surface across + multiple lines + - fixed left/right viewport docking survives inline-flow containers while + later normal flow stays below on the real Win32 surface + - forgiving stylesheet selector lists preserve the valid visual branch while + suppressing the duplicate invalid-branch artifact on the real Win32 surface + - flex-grow rows now expand the middle item between bounded red/blue siblings + on the real Win32 surface + - slow image loads are present before screenshot export succeeds + - delayed timer-driven content is present in the screenshot export path +- legacy real-site compatibility moved another step forward: + - CSS shorthand expansion now lifts basic `background`, `border`, and + `font` declarations into the longhands the current headed painter actually + consumes + - native table-family elements plus legacy presentational HTML attributes now + produce a real centered table search layout instead of flattening into + generic block flow + - simple `float:left` / `float:right` docking now keeps later body flow below + the float band on the headed surface + - percentage child heights now resolve from an explicit ancestor height when + available instead of incorrectly expanding to the full viewport in common + cases like search-box inputs + - bounded headed probes now cover both the legacy centered table search shape + and left/right float docking, and a fresh live `google.com` capture no + longer crashes while visibly benefiting from the shorthand/background + compatibility slice +- the next positioned/stability slice is now landed: + - later absolutely positioned siblings now anchor to the containing block + instead of being re-based off the evolving child flow cursor + - positioned boxes and interactive regions now carry effective `z-index` + order through both headed paint and headed hit-testing, so overlapping + later-row overlays can paint and click in the expected topmost order + - generic block descendants now paint after the ancestor background/border + phase instead of being hidden under later parent box fills on the shared + display-list path + - explicit CSS `height` no longer pollutes own-content height for generic + block containers, so normal-flow children stop being pushed down by the + full container box height + - headed screenshot export now also rejects tiny early paint noise instead of + treating a 1x1 placeholder draw as a ready frame + - bounded headed probes now cover absolute positioned overlap with topmost + click targeting plus the tiny-placeholder screenshot race in addition to + the earlier delayed-content screenshot gate +- CSS background-image compatibility now also has a first real box-paint slice: + - inline `background:` shorthand now expands basic `url(...)`, repeat, and + position tokens into `background-image`, `background-repeat`, and + `background-position` + - headed background images now paint through the shared image request path + with box clipping plus `repeat-x`, `repeat-y`, and `no-repeat` tiling on + the Win32 surface instead of being ignored outright + - bounded tests and a headed sprite probe now prove repeated and non-repeated + background image boxes render with the expected offset and size on the real + screenshot path +- box paint now also honors a practical uniform `border-radius` on headed + content boxes: + - fill and stroke rect commands now carry a corner radius through the shared + display-list path + - the Win32 headed surface now draws rounded fills and rounded borders for + nonzero radius boxes instead of flattening everything to square corners + - bounded tests plus a headed screenshot probe now prove a rounded pill box + clears its corners while an otherwise identical square box still fills its + corners on the real screenshot path +- intrinsic replaced-element sizing and richer background sizing/positioning + are now covered on the headed surface: + - `img` layout now uses natural dimensions and aspect-ratio backfill when + width or height is omitted, instead of defaulting generic block boxes to + container width + - responsive `img` layout now clamps through `max-width` while preserving + aspect ratio on the real headed surface + - CSS background parsing no longer loses later declarations after an + unquoted `url(...)`, and background shorthand now lifts `background-size` + in addition to image/repeat/position + - headed background images now carry semantic `background-position` + modes for pixel offsets, keywords, and percentages instead of collapsing + everything into raw offsets + - headed background images now also honor first-pass `background-size` + semantics for `contain`, `cover`, explicit lengths, and percentages on the + Win32 surface instead of always painting at natural size + - bounded tests plus headed probes now cover intrinsic image sizing, + responsive image shrink-to-fit behavior, semantic background positioning, + and semantic background sizing on the real screenshot path + - `object-fit`, `object-position`, and `aspect-ratio` now ride the same + headed image path, with display-list preservation plus headed probes + proving fill/contain/cover/none/scale-down placement and explicit aspect + ratio backfill on the real screenshot path + - explicit `box-sizing: content-box` now expands headed block and control + boxes by their padding on the real surface instead of treating all used + sizes as border-box, with a focused display-list regression and headed + width/height probe proving the content-box expansion path + - `overflow:hidden` now clips both painted descendants and headed + interaction for block and flex containers, while generic block/flex + `height`, `min-height`, and `max-height` now affect the used box height + instead of acting like loose hints + - overflow:auto/scroll containers now track client/scroll metrics, clamp + scroll offsets, and headed wheel input scrolls the targeted element before + falling back to presentation scrolling + - `document.elementFromPoint(...)` now respects ancestor overflow clipping, + and headed Win32 input no longer synthesizes anchor/control clicks through + the DOM path when no rendered link/control region exists at that client + point + - stylesheet rule application now honors selector specificity and source + order, including inline-style precedence and `!important`, so nested tab + labels and legacy Google-style nav clusters keep the intended colors and + weights +- the remaining gap is still large: this is a pragmatic compatibility slice, + not a full layout engine + +### Gate 4: Paint, Text, And Compositing + +Status: Planned + +Goal: +- turn the current simple painter into a real browser rendering pipeline + +Exit criteria: +- font loading and text shaping are good enough for mainstream sites +- CSS backgrounds, borders, opacity, transforms, clipping, and stacking are + implemented at an MVP level +- image rendering uses the browser resource pipeline +- canvas, SVG, and screenshot fidelity materially improve +- dirty-region invalidation avoids full-frame redraws for common interactions + +Current slice in progress: +- the next paint slice should build on the cached layout-box bridge by + hardening border/background/stacking edge cases and broader dirty-region + invalidation, so mainstream pages like Google stay visually stable as more + layout states settle on the real Win32 surface + +### Gate 5: Editing, Forms, And App Interactivity + +Status: Planned + +Goal: +- make normal website interaction feel dependable + +Exit criteria: +- text selection, clipboard, caret movement, IME, drag/drop, and pointer + capture are stable +- buttons, selects, checkboxes, radios, and file inputs behave correctly +- contenteditable and common rich-text editing flows work to a practical level +- keyboard shortcuts and accessibility-driven focus behavior are coherent + +### Gate 6: Modern Web Platform Coverage + +Status: Planned + +Goal: +- reach enough platform compatibility for mainstream browsing, not just simple + pages + +Exit criteria: +- robust fetch/XHR/WebSocket/navigation/history behavior +- storage APIs needed by common apps are implemented and persistent +- module/script loading and common JS integration paths are reliable +- workers and other core async primitives cover representative real sites + +### Gate 7: Tabs, Session Management, And Recovery + +Status: Planned + +Goal: +- make longer user sessions safe and practical + +Exit criteria: +- persistent session restore +- crash recovery and restart restore +- per-tab loading/crash/error state +- memory cleanup on tab close and navigation churn + +### Gate 8: Performance, Stability, And Security + +Status: Planned + +Goal: +- raise the fork from experimental to something users can trust + +Exit criteria: +- bounded memory/performance targets for long sessions +- crash logging and reproducible issue reports +- regression probes in CI for headed browsing on Windows +- clear security posture for cookies, storage, network policy, and unsafe + content handling + +### Gate 9: Packaging And Production Readiness + +Status: Planned + +Goal: +- ship a browser, not just a buildable developer project + +Exit criteria: +- Windows installer/package and portable build +- versioned releases and upgrade path +- default profile directory and migration behavior +- documentation for install, troubleshoot, and recover + +## Acceptance Bar For Production + +Treat the browser as production-ready only when all of these are true: + +- a normal user can install it and browse daily sites without needing CDP +- common login, search, reading, download, upload, and form flows work +- multi-tab browsing is stable for long sessions +- rendering quality is good enough that users do not need another browser to + visually verify the page +- core crash, stop, reload, navigation, and recovery paths are predictable + +## Working Rule For Future Milestones + +Prefer milestones that move the product from "experimental headed demo" toward +"installable minimalist browser." If a change only helps automation but does +not materially improve the browser product, it should usually rank below work +that advances the gates above. diff --git a/docs/HEADED_MODE_PRODUCTION_EXECUTION_GUIDE.md b/docs/HEADED_MODE_PRODUCTION_EXECUTION_GUIDE.md new file mode 100644 index 000000000..66d039c64 --- /dev/null +++ b/docs/HEADED_MODE_PRODUCTION_EXECUTION_GUIDE.md @@ -0,0 +1,837 @@ +# Headed Mode Production Execution Guide + +This document is for an assistant working inside the Lightpanda headed fork. +It is not a brainstorm. It is the execution order for taking the current fork +from "headed foundation with many working slices" to "production-ready +minimalist browser for real daily use". + +Read this together with: +- `docs/FULL_BROWSER_MASTER_TRACKER.md` +- `docs/HEADED_MODE_ROADMAP.md` +- `docs/WINDOWS_FULL_USE.md` + +The branch to treat as product truth is: +- `fork/headed-mode-foundation` + +## Product Bar + +The first production cut must satisfy all of these: +- headed browsing on Windows is a first-class product mode, not a demo +- normal users can browse common sites without relying on CDP automation +- shell UX is stable: tabs, address bar, back/forward/reload/stop, downloads, + bookmarks, history, settings, session restore, crash recovery +- rendered output is good enough for mainstream sites, not just localhost probes +- storage and network policy are coherent across tabs and restart +- screenshots use the same surface the user sees +- headless and CDP continue to work and are not regressed by headed work +- build and validation workflow self-recovers from known transient Windows/Zig + cache failures + +## Current Baseline + +Assume these are already in place unless a regression proves otherwise: +- native Win32 headed window lifecycle +- address bar, back, forward, reload, stop +- tab strip, duplicate, reopen closed tab, session restore +- internal `browser://start`, `browser://tabs`, `browser://history`, + `browser://bookmarks`, `browser://downloads`, and `browser://settings` +- persisted cookies, localStorage, IndexedDB, downloads, bookmarks, settings, + telemetry/profile identity +- file upload and attachment download promotion +- image, stylesheet, script, font, and authenticated subresource loading +- basic canvas 2D drawing plus first WebGL slices +- bounded headed probe coverage across the existing `tmp-browser-smoke/` suites +- Windows/MSVC build success for the fork + +Do not spend time re-solving those unless they are broken again. + +## Architecture Map + +Use these files as the primary ownership map: + +- App and runtime wiring + - `src/App.zig` + - `src/Config.zig` + - `src/main.zig` + - `src/lightpanda.zig` +- profile and host-path handling + - `src/HostPaths.zig` +- display backend and shell command bridge + - `src/display/Display.zig` + - `src/display/win32_backend.zig` + - `src/display/BrowserCommand.zig` +- paint and presentation path + - `src/render/DisplayList.zig` + - `src/render/DocumentPainter.zig` +- browser core and page/session behavior + - `src/browser/Browser.zig` + - `src/browser/Page.zig` + - `src/browser/EventManager.zig` +- network/runtime plumbing + - `src/http/` + - `src/browser/webapi/net/` +- HTML/CSS/DOM/Web APIs + - `src/browser/webapi/` + - `src/browser/webapi/element/html/` +- canvas and graphics + - `src/browser/webapi/canvas/CanvasRenderingContext2D.zig` + - `src/browser/webapi/canvas/CanvasSurface.zig` + - `src/browser/webapi/canvas/OffscreenCanvas.zig` + - `src/browser/webapi/canvas/WebGLRenderingContext.zig` +- smoke and acceptance probes + - `tmp-browser-smoke/` + +## Non-Negotiable Rules + +1. Keep headed rendering, screenshots, and hit-testing on one shared surface. + Do not add screenshot-only or probe-only rendering paths. +2. When a site or probe fails, fix the shared engine path, not the single page. +3. Every substantive slice must land with: + - a focused Zig test where possible + - a bounded headed probe for the real Win32 surface +4. Preserve headless and CDP behavior. +5. Do not delete `.lp-cache-win` for routine build recovery; it is expensive to + rebuild and usually not the real problem. +6. Treat stale logs as stale until reproduced. Old `_link_trace.log` style + failures are not proof of the current blocker. + +## Delivery Checkpointing + +When the run is organized into fixed-deliverable slices: +- after every verified batch of 25 completed deliverables, make a normal + commit and push it to the current fork branch head +- do not wait for a much larger slice to finish before checkpointing +- keep verification ahead of the commit and push so each checkpoint is a real + recovery point + +## Build Self-Recovery Routine + +If a Windows build times out or fails before real compiler diagnostics: + +1. Check for orphaned processes first. + - Inspect `zig`, `cargo`, `ninja`, `build.exe`, `cl`, `link`, and + `lld-link`. + - Kill only confirmed orphan PIDs. +2. Capture fresh logs. + - `zig build -Dtarget=x86_64-windows-msvc --summary all 1> tmp-current-build.stdout.txt 2> tmp-current-build.stderr.txt` +3. Classify. + - `failed to spawn build runner ... build.exe: FileNotFound` means default + `.zig-cache` corruption. + - `GetLastError(5): Access is denied` while Zig tries to spawn children + usually means an environment restriction, not a source break. + - only direct parser/type/linker diagnostics justify code edits +4. Validate the toolchain separately. + - `zig build --help` + - a tiny direct `zig build-exe` probe + - retry with fresh cache dirs: + - `--cache-dir .zig-cache-recover` + - `--global-cache-dir .zig-global-cache-recover` +5. If fresh-cache build works, recover normal operation with: + - `powershell -ExecutionPolicy Bypass -File .\scripts\windows\manage_build_artifacts.ps1 -CleanBuildCaches` +6. Re-run the default build and continue only after it succeeds. + +Timeout budgets: +- warm rebuild: about 5 minutes +- cold/fresh-cache build: 15 to 20 minutes + +## Definite Execution Order + +Do the remaining work in this order. Do not jump ahead to packaging before the +release gates below are genuinely green. + +### Phase 0: Build, Test, and Probe Discipline + +Objective: +- make the fork routine to build, diagnose, and validate + +Primary files: +- `scripts/windows/manage_build_artifacts.ps1` +- `docs/WINDOWS_FULL_USE.md` +- `build.zig` +- probe scripts under `tmp-browser-smoke/` + +Tasks: +- keep the build-cache recovery workflow documented and stable +- standardize captured build logs for every long Windows build +- convert the existing smoke directories into named gate suites, not ad hoc runs +- separate warm-build expectations from cold-build expectations in docs +- ensure the main validation runbook tells future assistants which probe family + to run for each subsystem change + +Exit criteria: +- a future assistant can recover from corrupted `.zig-cache` without guessing +- every major subsystem has a known bounded probe entry point +- cold build timing no longer gets misclassified as a hang + +### Phase 1: Rendering and Composition Fidelity + +Objective: +- make the visible headed surface reliable enough for mainstream sites + +Primary files: +- `src/render/DocumentPainter.zig` +- `src/render/DisplayList.zig` +- `src/display/win32_backend.zig` +- `src/browser/webapi/element/html/Image.zig` + +Tasks: +- complete the remaining high-value CSS/layout fidelity gaps in the shared + display-list path +- strengthen clipping, overflow, and compositing behavior for nested content +- improve image placement, scaling, alpha, and transformed hit-testing parity +- ensure caret, selection, focus rings, and control states remain visually + coherent during scroll and zoom +- remove any remaining placeholder presentation behavior that is still visible + on real pages +- prioritize failures that affect text-heavy pages, commerce/product pages, and + documentation sites before edge-case art demos + +Acceptance: +- `tmp-browser-smoke/layout-smoke` +- `tmp-browser-smoke/inline-flow` +- `tmp-browser-smoke/flow-layout` +- `tmp-browser-smoke/rendered-link-dom` +- `tmp-browser-smoke/showcase` + +Exit criteria: +- pages no longer depend on dummy layout/presentation behavior to remain usable +- the visible surface is the same surface used by screenshots and hit-testing + +### Phase 2: Text, Fonts, Editing, and IME + +Objective: +- make reading and typing feel native enough for real use + +Primary files: +- `src/display/win32_backend.zig` +- `src/render/DocumentPainter.zig` +- `src/browser/webapi/element/html/Input.zig` +- `src/browser/webapi/element/html/TextArea.zig` +- `src/browser/webapi/element/html/Label.zig` + +Tasks: +- finish the Windows text-input polish that docs already call out as incomplete: + IME candidate UI, composition edge cases, dead keys, and keyboard-layout + correctness +- improve text measurement and font fallback for mixed-family real pages +- validate selection, clipboard, caret movement, focus traversal, and form + editing against more realistic pages +- keep headed text metrics consistent with canvas text metrics and DOM geometry +- ensure zoom and DPI scaling do not break caret or text-control behavior + +Acceptance: +- `tmp-browser-smoke/form-controls` +- `tmp-browser-smoke/font-smoke` +- `tmp-browser-smoke/font-render` +- `tmp-browser-smoke/find` +- `tmp-browser-smoke/zoom` + +Exit criteria: +- users can reliably type, edit, paste, select, and navigate forms on the real + surface without input corruption or visual desync + +### Phase 3: Canvas and Graphics Completion + +Objective: +- move from early canvas/WebGL slices to "common web graphics just work" + +Primary files: +- `src/browser/webapi/canvas/CanvasRenderingContext2D.zig` +- `src/browser/webapi/canvas/CanvasPath.zig` +- `src/browser/webapi/canvas/CanvasSurface.zig` +- `src/browser/webapi/canvas/OffscreenCanvas.zig` +- `src/browser/webapi/canvas/WebGLRenderingContext.zig` +- `src/browser/webapi/element/html/Canvas.zig` + +Known high-value gaps to close first: +- `CanvasRenderingContext2D.zig` still carries no-op transforms: + `save`, `restore`, `scale`, `rotate`, `translate`, `transform`, + `setTransform`, `resetTransform` +- `CanvasRenderingContext2D.zig` still carries no-op or partial path APIs: + `quadraticCurveTo`, `bezierCurveTo`, `arc`, `arcTo`, `clip` +- `OffscreenCanvas.zig` still has stubbed `convertToBlob` and + `transferToImageBitmap` + +Tasks: +- finish transform stack semantics and path rasterization needed by common chart + and editor libraries +- complete clipping and compositing semantics used by canvases embedded in real + layouts +- advance OffscreenCanvas enough for libraries that move rendering off the main + canvas object +- continue WebGL from clear/basic triangle support to the minimum viable buffer, + shader, texture, and resize behavior needed by common UI/chart use +- make canvas-backed hit-testing, screenshots, and paints stay consistent + +Acceptance: +- `tmp-browser-smoke/canvas-smoke` +- any new focused graphics probes added for transforms, clipping, and blob/image + export + +Exit criteria: +- mainstream canvas/chart pages and simple WebGL pages render on the headed + surface without obvious placeholder behavior + +### Phase 4: DOM, CSS, and Web API Compatibility + +Objective: +- close the high-frequency API gaps that cause real sites to branch away or fail + +Primary files: +- `src/browser/webapi/Window.zig` +- `src/browser/webapi/Performance.zig` +- `src/browser/webapi/ResizeObserver.zig` +- `src/browser/webapi/XMLSerializer.zig` +- `src/browser/webapi/selector/` +- `src/browser/webapi/navigation/` +- `src/browser/webapi/element/html/` + +Known gaps worth prioritizing: +- `Window.alert` is still exposed as a noop +- `Performance` includes stub timing values +- `ResizeObserver` is skeletal +- `XMLSerializer` returns an empty structure +- `Slot` and some shadow/DOM details are still placeholder-level + +Tasks: +- prioritize APIs that modern frameworks use for layout, scheduling, routing, + and hydration +- improve selector and DOM mutation correctness only where it changes site + behavior, not for abstract spec score alone +- add the missing observer/performance/browser APIs required for real app shells +- keep invalid input contained to the page rather than crashing headed mode + +Acceptance: +- focused Zig tests per API family +- real-site or localhost probes for framework shells that previously branched + away because an API was stubbed + +Exit criteria: +- common app-shell frameworks stop failing on obvious missing API families + +### Phase 5: Networking, Navigation, and Security Correctness + +Objective: +- make real page loading behavior coherent across tabs, restarts, and protected + resources + +Primary files: +- `src/http/` +- `src/browser/webapi/net/` +- `src/browser/webapi/element/html/Link.zig` +- `src/browser/webapi/element/html/Style.zig` +- `src/browser/webapi/element/html/Image.zig` +- `src/lightpanda.zig` + +Tasks: +- continue tightening request policy for cookies, referer, auth, redirects, and + cache behavior across every subresource class +- ensure navigation failure handling, attachment handling, and popup policy stay + correct under restart, back/forward, and retry flows +- validate websocket, fetch-abort, credentialed fetch, stylesheet import, and + script/module behavior against realistic sequence timing +- harden download/file-path isolation and shell-action safety +- make sure profile persistence works identically whether the profile path is + absolute or repo-local relative + +Acceptance: +- `tmp-browser-smoke/image-smoke` +- `tmp-browser-smoke/stylesheet-smoke` +- `tmp-browser-smoke/fetch-abort` +- `tmp-browser-smoke/fetch-credentials` +- `tmp-browser-smoke/websocket-smoke` +- `tmp-browser-smoke/downloads` +- `tmp-browser-smoke/attachment-downloads` + +Exit criteria: +- cross-tab and restart behavior for network-backed features is deterministic +- protected subresources and downloads behave like one browser, not a set of + unrelated demos + +### Phase 6: Shell, Profile, and Product Polish + +Objective: +- turn the current shell into a product people can live in for long sessions + +Primary files: +- `src/lightpanda.zig` +- `src/display/BrowserCommand.zig` +- `src/display/win32_backend.zig` +- `src/HostPaths.zig` + +Tasks: +- polish tab UX, keyboard shortcuts, disabled states, focus behavior, and + internal-page navigation flows +- keep history/bookmarks/downloads/settings pages usable on long-lived profiles +- harden crash recovery, startup restore, homepage behavior, and error-page + recovery +- ensure native shell actions from downloads remain safe and predictable +- stabilize profile-path behavior across manual overrides, relative paths, and + future packaged installs + +Acceptance: +- `tmp-browser-smoke/browser-pages` +- `tmp-browser-smoke/tabs` +- `tmp-browser-smoke/settings` +- `tmp-browser-smoke/popup` +- `tmp-browser-smoke/file-upload` +- `tmp-browser-smoke/manual-user` + +Exit criteria: +- a user can browse, close, reopen, recover, download, and manage settings over + a long session without needing CDP or manual profile surgery + +### Phase 7: Reliability, Performance, and Crash Recovery + +Objective: +- make the browser trustworthy for repeated daily use + +Primary files: +- `src/App.zig` +- `src/crash_handler.zig` +- `src/lightpanda.zig` +- `src/display/win32_backend.zig` +- `src/render/DocumentPainter.zig` + +Tasks: +- add memory, startup, and steady-state performance checkpoints +- investigate leaks and unbounded growth across tabs, fonts, images, and canvas +- make crash capture and restart recovery explicit, not accidental +- audit long-session behavior for downloads, internal pages, and storage +- add soak runs that open, reload, close, and restore tabs repeatedly + +Acceptance: +- repeated headed probe loops without crash or runaway memory growth +- a manual soak script for long-lived headed sessions + +Exit criteria: +- the browser survives long sessions and repeated reopen cycles without obvious + degradation + +### Phase 8: Packaging, Install, and Release + +Objective: +- ship the fork as a usable Windows browser build, not just a local developer + artifact + +Primary files: +- `build.zig` +- `docs/WINDOWS_FULL_USE.md` +- packaging/release scripts added for this phase + +Tasks: +- define install layout, default profile location, and user-visible data paths +- package the executable and dependent runtime assets coherently +- keep first-run experience, logging, and update instructions clear +- document supported Windows version, known limitations, and fallback modes +- turn the current tracker state into a release checklist with explicit gates + +Exit criteria: +- a new user can install, launch, browse, and find their profile/downloads + without reading source code + +## Release Gate Matrix + +Do not call the fork production ready until these suites are green in headed +mode on the release candidate build: + +- shell and navigation + - `tmp-browser-smoke/tabs` + - `tmp-browser-smoke/browser-pages` + - `tmp-browser-smoke/settings` + - `tmp-browser-smoke/wrapped-link` + - `tmp-browser-smoke/popup` +- rendering and layout + - `tmp-browser-smoke/layout-smoke` + - `tmp-browser-smoke/inline-flow` + - `tmp-browser-smoke/font-render` + - `tmp-browser-smoke/image-smoke` +- forms and file handling + - `tmp-browser-smoke/form-controls` + - `tmp-browser-smoke/file-upload` + - `tmp-browser-smoke/downloads` + - `tmp-browser-smoke/attachment-downloads` +- storage and session + - `tmp-browser-smoke/cookie-persistence` + - `tmp-browser-smoke/localstorage-persistence` + - `tmp-browser-smoke/indexeddb-persistence` + - `tmp-browser-smoke/sessionstorage-scope` +- network/runtime + - `tmp-browser-smoke/fetch-abort` + - `tmp-browser-smoke/fetch-credentials` + - `tmp-browser-smoke/websocket-smoke` + - `tmp-browser-smoke/stylesheet-smoke` +- graphics + - `tmp-browser-smoke/canvas-smoke` + +Also require: +- successful default-cache Windows build +- successful fresh-cache Windows build +- one long manual session run on a non-trivial real-site mix + +## Bare Metal Path + +The bare-metal path is the next deployment target after the headed Windows +product path is green. It is not a separate browser. It is the same browser +core compiled against a narrower host surface. + +Do not fork browser behavior. Move OS assumptions out to explicit platform +services and keep the display list, browser pages, input model, and request +policy shared. + +### Bare Metal Target Definition + +- Boot directly into the Lightpanda shell on a freestanding or firmware-style + target. +- No Win32, no desktop shell, no implicit app-data directory, no dependence on + a host user profile. +- Use the same browser pages and same headed presentation pipeline semantics. +- Start on QEMU or an emulator image first. Hardware support comes after the + emulator path is stable. + +### Platform Seams + +- `src/App.zig`: stop assuming the platform is a desktop app with a process + profile directory. +- `src/HostPaths.zig`: split filesystem-backed profile resolution from the + abstract notion of a browser profile root. +- `src/display/Display.zig`: keep the backend boundary generic so Win32 and + bare-metal backends can share the same `DisplayList` contract. +- `src/display/win32_backend.zig`: treat as the hosted reference backend, not + the product architecture. +- `src/Net.zig` and `src/http/`: isolate sockets, timers, and I/O readiness + behind platform services. +- `src/crash_handler.zig` and `src/log.zig`: support non-console sinks such as + serial output or a ring buffer. +- `build.zig`: add a freestanding or bare-metal target class and select the + platform module at compile time. +- `src/sys/`: grow the platform-specific services for framebuffer, input, + timers, persistent storage, and optional network glue. + +### Bare Metal Execution Order + +#### Phase 9: Platform Service Boundary + +Objective: +- make the browser core compile against explicit host services instead of + Win32 or desktop assumptions + +Primary files: +- `src/App.zig` +- `src/HostPaths.zig` +- `src/lightpanda.zig` +- `src/display/Display.zig` +- `src/Net.zig` +- `build.zig` +- `src/sys/` + +Tasks: +- define a small host-service surface for: + - profile storage + - display surface + - input events + - clock and timers + - logging + - fatal exit / reboot hooks +- move direct `std.fs` usage in persistence and startup flows behind the host + storage service +- keep `Browser`, `Page`, `EventManager`, `DocumentPainter`, and + `DisplayList` free of firmware-specific code +- add a mock host implementation so unit tests can run without a windowing + system +- make sure the build system can select the hosted Windows backend or the + bare-metal backend without changing browser logic + +Acceptance: +- the browser core compiles with the mock host +- persistence and startup code paths are testable without Win32 +- the current Windows backend remains intact while the host seam is added + +Exit criteria: +- the browser no longer assumes desktop process semantics in core code + +#### Phase 10: Boot and Presentation + +Objective: +- get pixels and input on screen with the same shared display-list path + +Primary files: +- `src/display/Display.zig` +- `src/display/win32_backend.zig` +- `src/render/DisplayList.zig` +- `src/render/DocumentPainter.zig` +- `src/browser/EventManager.zig` +- `src/browser/Page.zig` +- `src/browser/webapi/element/html/Canvas.zig` +- `src/sys/` + +Tasks: +- implement a bare-metal display backend that consumes `DisplayList` and paints + to a framebuffer or equivalent compositor target +- implement a visible boot/loading state so startup failures are obvious +- wire keyboard and pointer input into the same browser event path used by the + Windows backend +- keep screenshots and visible output semantically identical to the headed + Win32 path +- choose one initial boot stack and keep it narrow: + - QEMU or emulator framebuffer first + - then physical hardware +- do not create a second renderer just for the boot path + +Acceptance: +- boot lands in the browser shell +- `browser://start` renders on the bare-metal surface +- click, type, scroll, and tab switching work in the boot image +- framebuffer screenshots are reproducible from the same presentation state + +Exit criteria: +- the same browser UI can be driven on the bare-metal surface without desktop + dependencies + +#### Phase 11: Persistence and Networking + +Objective: +- make browser state survive power loss and restart on a non-desktop target + +Primary files: +- `src/HostPaths.zig` +- `src/lightpanda.zig` +- `src/browser/webapi/storage/` +- `src/browser/webapi/net/` +- `src/http/` +- `src/browser/webapi/element/html/Link.zig` +- `src/browser/webapi/element/html/Style.zig` +- `src/browser/webapi/element/html/Image.zig` + +Tasks: +- back profile data with a durable bare-metal store instead of host app-data + paths +- preserve cookies, localStorage, IndexedDB, bookmarks, downloads, settings, + and session state across reboot +- expose or emulate the minimum storage semantics needed by the existing + browser pages +- bring up networking with the smallest viable path that supports HTTP, HTTPS, + redirects, cookies, and downloads +- keep request policy identical to the Windows path for protected resources +- make failures explicit when storage or network hardware is absent + +Acceptance: +- profile state survives reboot +- navigation, downloads, and storage-backed browser pages work after restart +- protected subresource behavior matches the Windows path + +Exit criteria: +- the browser can be used across power cycles without losing the user profile + +#### Phase 12: Boot Image and Release + +Objective: +- produce a bootable browser image that can be tested and shipped + +Primary files: +- `build.zig` +- `docs/WINDOWS_FULL_USE.md` +- packaging and release scripts added for this phase + +Tasks: +- add boot-image packaging to the build or adjacent packaging scripts +- define image layout, firmware assumptions, and required device support +- document supported emulator or hardware classes, memory floor, input devices, + and network devices +- create repeatable boot smoke scripts and artifact capture +- keep the concrete packaging entry point at + `scripts/windows/package_bare_metal_image.ps1` and drive the launch smoke + from `tmp-browser-smoke/bare-metal-release/chrome-bare-metal-image-probe.ps1` + so a future assistant can reproduce the bundle without guessing +- keep the release gate honest by also running + `tmp-browser-smoke/bare-metal-release/chrome-bare-metal-policy-probe.ps1` + against the localhost request-policy harness so the same browser path proves + both launch readiness and request-policy parity +- keep the bare-metal restart path honest by also running + `tmp-browser-smoke/bare-metal-release/chrome-bare-metal-tabs-session-restore-probe.ps1` + so the packaged binary proves tab opening, switching, closing, and session + restore across a restart with the same profile directory +- keep the profile persistence path honest by also running + `tmp-browser-smoke/bare-metal-release/chrome-bare-metal-persistence-probe.ps1` + so the packaged binary proves bookmarks, homepage, settings writes, and + restore-session changes survive a restart with the same profile directory +- keep the storage-layer path honest by also running the cookie, + localStorage, and IndexedDB restart probes under + `tmp-browser-smoke/bare-metal-release/` so the packaged binary proves the + durable storage layer survives power loss for all three state buckets +- use `zig build bare_metal_release -Dtarget=x86_64-windows-msvc -Dtarget_class=bare_metal` + as the canonical end-to-end validation command for the package-and-smoke + slice on Windows +- expect the release bundle archive at + `tmp-browser-smoke/bare-metal-release.zip` and keep the smoke artifacts + under `tmp-browser-smoke/bare-metal-release/` +- keep a fast recovery path for image boot failures + +Acceptance: +- a bootable image launches the browser shell in an emulator or on hardware +- the same smoke suites have a bare-metal execution mode +- startup, navigation, and restart survive repeated boot cycles + +Exit criteria: +- the bare-metal path can be delivered and reproduced without source edits + +### Bare Metal Release Gate + +Do not call the bare-metal path production ready until: +- the image boots reliably on the chosen emulator and at least one target + hardware class +- the browser shell is usable with keyboard and pointer input +- profile state persists across reboot +- the network path handles normal navigation and downloads, and the packaged + release smoke set includes `chrome-bare-metal-image-probe.ps1`, + `chrome-bare-metal-policy-probe.ps1`, + `chrome-bare-metal-download-probe.ps1`, and + `chrome-bare-metal-start-shell-probe.ps1`, + `chrome-bare-metal-tabs-session-restore-probe.ps1`, and + `chrome-bare-metal-persistence-probe.ps1`, plus the cookie, localStorage, + and IndexedDB persistence probes, including Ctrl+L address commit and + restart-persistence coverage for bookmarks, settings, and storage buckets +- the same core browser pages work without Win32 +- boot and runtime failures are reproducible from saved logs + +### Bare Metal Validation Rule + +Every bare-metal slice must end with: +1. a compile check for the bare-metal target or the nearest supported host + equivalent +2. the relevant unit tests for the modules that changed +3. the relevant smoke/probe suite for the surface that changed +4. saved logs or artifacts for any failure + +Do not move to the next bare-metal layer until the current layer compiles and +its validation passes. + +### Bare Metal Module Split + +Keep the bare-metal host code in `src/sys/` and related build glue, not in the +browser core. + +Split the first bring-up into these host modules: +- `src/sys/boot.zig` for startup, panic routing, and shutdown +- `src/sys/framebuffer.zig` for the pixel surface and screenshot capture +- `src/sys/input.zig` for keyboard and pointer event ingestion +- `src/sys/timer.zig` for monotonic time, sleeps, and animation pacing +- `src/sys/storage.zig` for profile persistence and file emulation +- `src/sys/net.zig` for the transport and socket/driver shim +- `src/sys/serial_log.zig` for log output when no desktop console exists + +Rules: +- browser code never talks to drivers directly +- the display backend consumes a generic surface, not a boot-specific API +- persistence goes through the storage service, not raw block I/O in browser + code +- request policy stays in the shared HTTP/browser layers; only the transport + changes +- the boot module owns the initial run loop and the last-resort failure path + +### Bare Metal Smoke Order + +Bring up the image in this order: +1. boot banner plus panic output +2. framebuffer fill and browser chrome paint +3. keyboard focus, text entry, and mouse click delivery +4. `browser://start` and `browser://tabs` +5. profile save/restore across a restart +6. network navigation to a localhost smoke page +7. downloads and storage-backed browser pages +8. one real-site fetch with the same request policy as Windows + +If a step fails, fix the earliest missing layer. Do not debug later layers +before the current one is stable. + +### Bare Metal Batch 1: 25 Deliverables + +This is the first concrete bare-metal checkpoint batch. Complete the items in +order. Do not skip ahead. Every item still follows the Bare Metal Validation +Rule, and the whole batch ends with the commit-and-push checkpoint rule. + +1. Add `src/sys/host.zig` with the canonical host-service interface for + storage, display, input, timer, logging, and power control. Exit check: the + browser core compiles against the interface without Win32 imports in core + files. +2. Add a mock host implementation for unit tests. Exit check: startup and + persistence tests run without a windowing system or firmware target. +3. Refactor `src/App.zig` to accept host services instead of assuming desktop + process semantics. Exit check: app startup no longer depends on implicit + app-data or desktop globals. +4. Add `src/sys/boot.zig` for startup, panic routing, and shutdown. Exit check: + boot code can emit a visible failure and terminate cleanly. +5. Add `src/sys/serial_log.zig` for non-console logging. Exit check: fatal + errors can be captured on a serial sink or ring buffer. +6. Add `src/sys/timer.zig` for monotonic time, sleeps, and pacing. Exit check: + animation and event loops can advance without `std.time` calls in browser + core code. +7. Add `src/sys/input.zig` for keyboard and pointer ingestion. Exit check: a + platform test can inject keypress and click events deterministically. +8. Add `src/sys/framebuffer.zig` for pixel output and screenshot capture. + Exit check: a framebuffer test can draw and read back pixels. +9. Add `src/sys/storage.zig` for profile root resolution and durable file + operations. Exit check: profile files can be created, reopened, and + enumerated on the mock host. +10. Add `src/sys/net.zig` for transport and socket glue. Exit check: the + network shim can be swapped without changing browser request policy code. +11. Extend `build.zig` with a target-class switch for hosted vs bare-metal + builds. Exit check: both target classes can be selected without editing + browser logic. +12. Refactor `src/HostPaths.zig` to use profile file and subdir helpers for + bare-metal-safe path resolution. Exit check: profile file paths resolve in + tests and on the mock host. +13. Replace direct profile-directory assumptions in `src/lightpanda.zig` with + host-safe directory helpers. Exit check: cookies, settings, session, + bookmarks, and downloads still round-trip under the hosted backend. +14. Add compile tests that instantiate browser core types with the mock host. + Exit check: `Browser`, `Page`, `EventManager`, `DisplayList`, and + `DocumentPainter` compile without Win32-specific branches. +15. Add the bare-metal display backend skeleton that consumes `DisplayList`. + Exit check: the backend can accept a list and paint a trivial frame. +16. Add a visible boot and loading state. Exit check: startup failures are + obvious on the framebuffer instead of vanishing into a black screen. +17. Wire keyboard focus and text entry through the bare-metal input path. Exit + check: address bar focus and text input work in the shell. +18. Wire pointer input through the bare-metal input path. Exit check: links and + controls are activatable with a pointer. +19. Bring up `browser://start` on the bare-metal surface. Exit check: the + start page renders and responds to interaction. +20. Bring up `browser://tabs` and tab switching on the bare-metal surface. + Exit check: opening, switching, and closing tabs work across a restart. +21. Back profile persistence for bookmarks, settings, and session state with + the durable storage layer. Exit check: the data survives a restart in the + emulator. +22. Back cookies, localStorage, and IndexedDB with the durable storage layer. + Exit check: storage-backed browser pages still work after power loss. +23. Implement the minimal bare-metal HTTP and HTTPS navigation path. Exit + check: localhost navigation and + `tmp-browser-smoke/bare-metal-release/chrome-bare-metal-download-probe.ps1` + pass on the emulator. +24. Verify one real-site fetch on bare metal with the same request policy as + Windows. Exit check: the same policy gates content on both hosted and + bare-metal paths. +25. Package a bootable image and emulator launch smoke with artifact capture. + Exit check: a repeatable image launch can be driven without source edits. + +## Anti-Patterns + +Do not do these: +- do not add probe-only rendering behavior that real pages cannot use +- do not special-case one site if the underlying engine path is still wrong +- do not treat old logs as current truth +- do not mark a phase done because a single localhost probe turned green +- do not delete dependency caches to solve a transient local Zig cache issue + +## Definition Of Done + +The fork is production ready only when: +- the default Windows headed build is routine and self-recovering +- the shell is stable for long daily sessions +- common sites render and interact correctly enough for ordinary use +- the existing smoke matrix is green and organized as a release gate +- the remaining obvious API stubs no longer drive common sites off the happy + path +- the product can be installed and used by someone who is not inside the repo +- the bare-metal path has a bootable browser image, persistent profile, working + input and networking, and its own reproducible smoke gates diff --git a/docs/HEADED_MODE_ROADMAP.md b/docs/HEADED_MODE_ROADMAP.md new file mode 100644 index 000000000..e0f81d344 --- /dev/null +++ b/docs/HEADED_MODE_ROADMAP.md @@ -0,0 +1,55 @@ +# Headed Mode Roadmap (Fork) + +This fork targets full headed-mode browser usage while preserving Lightpanda's +current headless strengths. + +For the full product plan from the current headed foundation to a +production-ready minimalist Zig browser, see +`docs/FULL_BROWSER_MASTER_TRACKER.md`. + +## Current Status + +- `--browser_mode headless|headed` is now accepted. +- `--headed` and `--headless` shortcuts are available. +- On Windows targets, `headed` now starts a native window lifecycle backend. +- On non-Windows targets, `headed` still uses a safe headless fallback with warning. +- `--window_width` / `--window_height` now drive window/screen/viewport values. +- Display runtime abstraction exists with page lifecycle hooks and a Win32 thread backend. +- CDP viewport APIs update runtime viewport (`Emulation.*Metrics*`, `Browser.setWindowBounds`). +- Win32 headed backend now forwards native mouse (down/up/move/wheel/hwheel), click, keydown/keyup, text input (`WM_CHAR`/`WM_UNICHAR`), IME result/preedit composition messages (`WM_IME_COMPOSITION`), back/forward mouse buttons, and window blur events into page input. +- Win32 headed backend now propagates native key repeat state into `KeyboardEvent.repeat`. +- Text control editing now includes caret-aware insertion paths, `Ctrl/Meta + A` select-all, word-wise keyboard edit/navigation shortcuts, textarea vertical/line navigation, `Tab`/`Shift+Tab` focus traversal with `tabindex` ordering, and native clipboard shortcuts (`Ctrl/Meta + C/X/V`, `Ctrl+Insert`, `Shift+Insert`, `Shift+Delete`) with cancelable clipboard event dispatch. +- Windows prereq checker + runbook added (`scripts/windows`, `docs/WINDOWS_FULL_USE.md`). + +## Milestones + +1. Display abstraction +- Introduce a renderer backend interface with a no-op backend and a + real windowed backend (Win32 lifecycle backend implemented). +- Keep DOM, JS, networking, and CDP independent from the window backend. + +2. Window lifecycle +- Implement window creation, resize, close, and frame pump. +- Wire browser/page lifecycle events to the display backend. + +3. Layout and paint pipeline +- Build incremental layout + paint passes from DOM/CSS state. +- Add dirty-region invalidation to avoid full-frame redraws. + +4. Input + event synthesis +- Convert OS input events (mouse/keyboard/wheel/focus) into DOM events. +- Keep CDP input paths consistent with native input behavior. + +5. Screenshots and surfaces +- Expose pixel surfaces for screenshots/recording while in headed mode. +- Ensure parity between headless and headed screenshot semantics. + +6. Stabilization +- Add headed integration tests (window lifecycle, input, rendering, resize). +- Validate performance, memory, and crash-handling budgets. + +## Design Constraints + +- No regressions to existing headless CLI/CDP behavior. +- Feature flags must keep partial implementations safe. +- Keep platform-specific code isolated behind backend boundaries. diff --git a/docs/WINDOWS_FULL_USE.md b/docs/WINDOWS_FULL_USE.md new file mode 100644 index 000000000..b6b3de122 --- /dev/null +++ b/docs/WINDOWS_FULL_USE.md @@ -0,0 +1,65 @@ +# Lightpanda Full Use on Windows (Fork) + +This fork now has: + +- Runtime browser mode switch (`--browser_mode headless|headed`) +- Runtime viewport controls (`--window_width`, `--window_height`) +- CDP viewport controls (`Emulation.setDeviceMetricsOverride`, `Emulation.clearDeviceMetricsOverride`, `Browser.setWindowBounds`) + +## 1) Check Windows prerequisites + +Run: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\windows\check_lightpanda_windows_prereqs.ps1 +``` + +If `SymlinkCreate` fails, enable Windows Developer Mode and reopen your shell. +Without symlink capability, Zig dependency unpacking can fail (`depot_tools`). +`DeveloperMode` can still show `FAIL` if symlink creation already works in your +current shell context. + +## 2) Build options + +1. Native Windows build: +- Works only when symlink creation is available in the current shell. +- Then run normal build commands (for example `zig build run -- help`). + +2. WSL build (recommended fallback): +- Build and run from WSL where symlink behavior is reliable. +- Connect automation clients from Windows host to the WSL endpoint. + +## 3) Runtime usage examples + +CLI: + +```powershell +.\lightpanda.exe serve --browser_mode headed --window_width 1366 --window_height 768 --host 127.0.0.1 --port 9222 +``` + +CDP viewport override: + +- `Emulation.setDeviceMetricsOverride` +- `Emulation.clearDeviceMetricsOverride` +- `Browser.setWindowBounds` with width/height + +## 4) Current headed status + +`headed` mode now has a native Windows window lifecycle backend: + +- window open/close with page lifecycle +- native Win32 message pump on a dedicated thread +- viewport resize wiring from CLI and CDP metrics/window-bounds APIs +- native mouse (down/up/move/wheel/hwheel), click, keydown/keyup, text input (`WM_CHAR`/`WM_UNICHAR`), IME result/preedit composition messages (`WM_IME_COMPOSITION`), back/forward mouse buttons, and window blur wired into page input handling +- native key repeat state is propagated to `KeyboardEvent.repeat` +- text controls now keep insertion at the active caret/selection and support `Ctrl/Meta + A` select-all +- text controls also support word-wise keyboard editing (`Ctrl/Meta + ArrowLeft/ArrowRight`, `Ctrl/Meta + Backspace/Delete`) +- textareas now support vertical and line-aware caret movement (`ArrowUp/ArrowDown`, line-aware `Home/End`, document `Ctrl/Meta + Home/End`) +- keyboard focus traversal now supports `Tab` / `Shift+Tab` with `tabindex` ordering +- native clipboard shortcuts are wired for text controls (`Ctrl/Meta + C/X/V`, `Ctrl+Insert`, `Shift+Insert`, `Shift+Delete`) +- clipboard shortcuts dispatch cancelable `copy`/`cut`/`paste` events and respect `preventDefault()` + +Graphical rendering and native input translation are still in-progress: + +- frame presentation pipeline +- IME candidate/composition UI and dead-key edge cases diff --git a/scripts/windows/check_lightpanda_windows_prereqs.ps1 b/scripts/windows/check_lightpanda_windows_prereqs.ps1 new file mode 100644 index 000000000..56808feea --- /dev/null +++ b/scripts/windows/check_lightpanda_windows_prereqs.ps1 @@ -0,0 +1,77 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Write-Status { + param( + [string]$Name, + [bool]$Ok, + [string]$Details + ) + $mark = if ($Ok) { "PASS" } else { "FAIL" } + Write-Host ("[{0}] {1} - {2}" -f $mark, $Name, $Details) +} + +$allOk = $true + +# 1) Developer mode (enables non-admin symlink creation on many setups) +$devModeKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" +$devModeValue = $null +try { + $devModeValue = (Get-ItemProperty -Path $devModeKey -Name AllowDevelopmentWithoutDevLicense -ErrorAction Stop).AllowDevelopmentWithoutDevLicense +} catch { + $devModeValue = 0 +} +$devModeEnabled = ($devModeValue -eq 1) +Write-Status "DeveloperMode" $devModeEnabled ("AllowDevelopmentWithoutDevLicense={0}" -f $devModeValue) + +# 2) Symlink capability test +$symlinkOk = $false +$tmpRoot = Join-Path $env:TEMP ("lightpanda_symlink_test_{0}" -f [Guid]::NewGuid().ToString("N")) +try { + New-Item -ItemType Directory -Path $tmpRoot -Force | Out-Null + $target = Join-Path $tmpRoot "target.txt" + $link = Join-Path $tmpRoot "link.txt" + Set-Content -Path $target -Value "ok" -Encoding UTF8 + New-Item -ItemType SymbolicLink -Path $link -Target $target -ErrorAction Stop | Out-Null + $symlinkOk = $true +} catch { + $symlinkOk = $false +} finally { + Remove-Item -Path $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue +} +Write-Status "SymlinkCreate" $symlinkOk "Create symbolic links in current shell" +if (-not $symlinkOk) { $allOk = $false } + +# 3) Zig +$zigVersion = $null +try { + $zigVersion = (zig version).Trim() +} catch { + $zigVersion = $null +} +$zigOk = ($null -ne $zigVersion -and $zigVersion.Length -gt 0) +$zigDetails = if ($zigOk) { "zig {0}" -f $zigVersion } else { "zig not found in PATH" } +Write-Status "Zig" $zigOk $zigDetails +if (-not $zigOk) { $allOk = $false } + +# 4) WSL availability (recommended fallback workflow) +$wslOk = $false +try { + $null = wsl.exe --status 2>$null + $wslOk = $true +} catch { + $wslOk = $false +} +$wslDetails = if ($wslOk) { "wsl.exe available" } else { "wsl.exe not available" } +Write-Status "WSL" $wslOk $wslDetails + +if ($allOk) { + Write-Host "" + Write-Host "Windows prerequisites look good for local Lightpanda development." + exit 0 +} + +Write-Host "" +Write-Host "One or more required prerequisites failed." +Write-Host "See docs/WINDOWS_FULL_USE.md for remediation." +exit 1 diff --git a/scripts/windows/manage_build_artifacts.ps1 b/scripts/windows/manage_build_artifacts.ps1 new file mode 100644 index 000000000..6b2565039 --- /dev/null +++ b/scripts/windows/manage_build_artifacts.ps1 @@ -0,0 +1,158 @@ +param( + [string]$RepoRoot = "C:\Users\adyba\src\lightpanda-browser", + [switch]$CleanBuildCaches, + [switch]$CleanDependencyCaches, + [switch]$CleanSliceOutputs +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Get-PathSizeBytes { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path)) { + return [int64]0 + } + + $sum = [int64]0 + foreach ($file in (Get-ChildItem -LiteralPath $Path -Recurse -Force -File -ErrorAction SilentlyContinue)) { + $sum += [int64]$file.Length + } + return $sum +} + +function Get-FileSizeBytes { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path)) { + return [int64]0 + } + + return [int64](Get-Item -LiteralPath $Path -Force).Length +} + +function Format-Size { + param( + [Parameter(Mandatory = $true)] + [int64]$Bytes + ) + + if ($Bytes -ge 1TB) { return "{0:N2} TB" -f ($Bytes / 1TB) } + if ($Bytes -ge 1GB) { return "{0:N2} GB" -f ($Bytes / 1GB) } + if ($Bytes -ge 1MB) { return "{0:N2} MB" -f ($Bytes / 1MB) } + if ($Bytes -ge 1KB) { return "{0:N2} KB" -f ($Bytes / 1KB) } + return "{0} B" -f $Bytes +} + +function New-ArtifactRecord { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$Category, + [Parameter(Mandatory = $true)] + [bool]$DefaultClean, + [Parameter(Mandatory = $true)] + [string]$Reason, + [Parameter(Mandatory = $true)] + [ValidateSet("directory", "file")] + [string]$Kind + ) + + $exists = Test-Path -LiteralPath $Path + $bytes = if (-not $exists) { + [int64]0 + } elseif ($Kind -eq "file") { + Get-FileSizeBytes -Path $Path + } else { + Get-PathSizeBytes -Path $Path + } + + [pscustomobject]@{ + Path = $Path + Name = Split-Path -Path $Path -Leaf + Kind = $Kind + Category = $Category + DefaultClean = $DefaultClean + Exists = $exists + SizeBytes = $bytes + Size = Format-Size -Bytes $bytes + Reason = $Reason + } +} + +function Remove-Artifact { + param( + [Parameter(Mandatory = $true)] + [pscustomobject]$Artifact + ) + + if (-not $Artifact.Exists) { + return + } + + if ($Artifact.Kind -eq "directory") { + Remove-Item -LiteralPath $Artifact.Path -Recurse -Force + } else { + Remove-Item -LiteralPath $Artifact.Path -Force + } +} + +$artifacts = @( + (New-ArtifactRecord -Path (Join-Path $RepoRoot ".zig-cache") -Category "Build cache" -DefaultClean $true -Reason "Transient Zig local cache. Safe to delete; next build will be cold." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot ".zig-cache-next") -Category "Build cache" -DefaultClean $true -Reason "Alternate transient Zig cache from local slice builds. Safe to delete." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "zig-out-slice") -Category "Slice output" -DefaultClean $true -Reason "Local slice output directory. Regenerated by slice builds." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "lightpanda-slice.exe") -Category "Slice output" -DefaultClean $true -Reason "Manual local slice binary. Safe to delete if not in use." -Kind "file"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "lightpanda-slice.pdb") -Category "Slice output" -DefaultClean $true -Reason "Debug symbols for manual local slice binary." -Kind "file"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "lightpanda-slice.lib") -Category "Slice output" -DefaultClean $true -Reason "Import library for manual local slice binary." -Kind "file"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "tmp-browser-smoke\bare-metal-release") -Category "Slice output" -DefaultClean $true -Reason "Packaged bare-metal launch bundle and its smoke artifacts." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "tmp-browser-smoke\bare-metal-release.zip") -Category "Slice output" -DefaultClean $true -Reason "Packaged bare-metal release archive." -Kind "file"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot ".lp-cache") -Category "Dependency cache" -DefaultClean $false -Reason "Bootstrap cache for downloaded tools/dependencies. Deleting is safe but costly to regenerate." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot ".lp-cache-win") -Category "Dependency cache" -DefaultClean $false -Reason "Windows V8/depot_tools cache. Large and expensive to rebuild." -Kind "directory"), + (New-ArtifactRecord -Path (Join-Path $RepoRoot "zig-out") -Category "Primary output" -DefaultClean $false -Reason "Current compiled outputs. Usually keep unless you want a full artifact reset." -Kind "directory") +) + +Write-Host "Lightpanda build artifact report" +Write-Host "" +$artifacts | + Sort-Object @{ Expression = "SizeBytes"; Descending = $true }, Name | + Select-Object Name,Category,Exists,DefaultClean,Size,Reason | + Format-Table -AutoSize | Out-String -Width 220 | + Write-Host + +$toRemove = @() +if ($CleanBuildCaches) { + $toRemove += $artifacts | Where-Object { $_.Exists -and $_.Category -eq "Build cache" } +} +if ($CleanSliceOutputs) { + $toRemove += $artifacts | Where-Object { $_.Exists -and $_.Category -eq "Slice output" } +} +if ($CleanDependencyCaches) { + $toRemove += $artifacts | Where-Object { $_.Exists -and $_.Category -eq "Dependency cache" } +} + +$toRemove = @($toRemove | Sort-Object Path -Unique) + +if ($toRemove.Count -eq 0) { + Write-Host "No cleanup requested. Use -CleanBuildCaches, -CleanSliceOutputs, and/or -CleanDependencyCaches." + exit 0 +} + +$totalBytes = ($toRemove | Measure-Object -Property SizeBytes -Sum).Sum +Write-Host "" +Write-Host ("Deleting {0} artifact(s), reclaiming about {1}." -f $toRemove.Count, (Format-Size -Bytes ([int64]$totalBytes))) + +foreach ($artifact in $toRemove) { + Write-Host ("Removing {0}" -f $artifact.Path) + Remove-Artifact -Artifact $artifact +} + +Write-Host "" +Write-Host "Cleanup complete." diff --git a/scripts/windows/package_bare_metal_image.ps1 b/scripts/windows/package_bare_metal_image.ps1 new file mode 100644 index 000000000..78d2dee52 --- /dev/null +++ b/scripts/windows/package_bare_metal_image.ps1 @@ -0,0 +1,210 @@ +[CmdletBinding()] +param( + [string]$RepoRoot, + [string]$BrowserExe, + [string]$PackageRoot, + [string]$Url = "https://example.com/", + [switch]$RunSmoke +) + +$ErrorActionPreference = 'Stop' + +$scriptRoot = $PSScriptRoot +if (-not $RepoRoot) { + $RepoRoot = (Resolve-Path (Join-Path $scriptRoot "..\..")).Path +} +if (-not $BrowserExe) { + $BrowserExe = Join-Path $RepoRoot "zig-out\bin\lightpanda.exe" +} +if (-not $PackageRoot) { + $PackageRoot = Join-Path $RepoRoot "tmp-browser-smoke\bare-metal-release\image" +} + +function New-BareMetalLaunchScript { + param( + [string]$LaunchScriptPath, + [string]$RepoRoot + ) + + $launchScript = @' +[CmdletBinding()] +param( + [string]$Url = "https://example.com/", + [string]$ScreenshotPath = $(Join-Path $PSScriptRoot "artifacts\launch.png"), + [string]$ProfileRoot = $(Join-Path $PSScriptRoot "profile") +) + +$ErrorActionPreference = 'Stop' + +if (-not ("BareMetalImageUser32" -as [type])) { + Add-Type @" +using System; +using System.Runtime.InteropServices; +using System.Text; + +public static class BareMetalImageUser32 { + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int GetWindowTextW(IntPtr hWnd, StringBuilder text, int count); +} +"@ +} + +$browserExe = Join-Path $PSScriptRoot "boot\lightpanda.exe" +if (-not (Test-Path $browserExe)) { + throw "bare metal image executable missing: $browserExe" +} + +New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ScreenshotPath) | Out-Null +New-Item -ItemType Directory -Force -Path $ProfileRoot | Out-Null + +$stdout = Join-Path $PSScriptRoot "artifacts\launch.stdout.txt" +$stderr = Join-Path $PSScriptRoot "artifacts\launch.stderr.txt" +$resultPath = Join-Path $PSScriptRoot "artifacts\launch-result.json" +if (Test-Path $ScreenshotPath) { Remove-Item $ScreenshotPath -Force } +if (Test-Path $stdout) { Remove-Item $stdout -Force } +if (Test-Path $stderr) { Remove-Item $stderr -Force } +if (Test-Path $resultPath) { Remove-Item $resultPath -Force } + +$psi = New-Object System.Diagnostics.ProcessStartInfo +$psi.FileName = $browserExe +$psi.Arguments = "browse --headed --window_width 1280 --window_height 720 --screenshot_png `"$ScreenshotPath`" `"$Url`"" +$psi.WorkingDirectory = $PSScriptRoot +$psi.UseShellExecute = $false +$psi.RedirectStandardOutput = $true +$psi.RedirectStandardError = $true +$psi.EnvironmentVariables["APPDATA"] = $ProfileRoot +$psi.EnvironmentVariables["LOCALAPPDATA"] = $ProfileRoot + +$process = New-Object System.Diagnostics.Process +$process.StartInfo = $psi +$process.Start() | Out-Null + +$stdoutTask = $process.StandardOutput.ReadToEndAsync() +$stderrTask = $process.StandardError.ReadToEndAsync() +$deadline = (Get-Date).AddSeconds(90) +$screenshotReady = $false +$windowTitle = "" + +while ((Get-Date) -lt $deadline) { + if (-not $process.HasExited -and $process.MainWindowHandle -ne 0) { + $titleBuffer = New-Object System.Text.StringBuilder 512 + [void][BareMetalImageUser32]::GetWindowTextW($process.MainWindowHandle, $titleBuffer, $titleBuffer.Capacity) + $windowTitle = $titleBuffer.ToString() + if ($windowTitle -like "*Example Domain*") { + $screenshotReady = $true + } + } + + if (Test-Path $ScreenshotPath) { + $item = Get-Item $ScreenshotPath + if ($item.Length -gt 0) { + $screenshotReady = $true + break + } + } + + if ($process.HasExited) { + break + } + + Start-Sleep -Milliseconds 250 +} + +if (-not $process.HasExited) { + Start-Sleep -Milliseconds 500 + if (-not $process.HasExited) { + Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue + } +} + +$stdoutTask.Wait() +$stderrTask.Wait() +$stdoutTask.Result | Set-Content -Path $stdout -Encoding Ascii +$stderrTask.Result | Set-Content -Path $stderr -Encoding Ascii + +$result = [ordered]@{ + pid = $process.Id + exited = $process.HasExited + exit_code = if ($process.HasExited) { $process.ExitCode } else { $null } + success = $screenshotReady + screenshot_ready = $screenshotReady + screenshot_exists = (Test-Path $ScreenshotPath) + screenshot_size = if (Test-Path $ScreenshotPath) { (Get-Item $ScreenshotPath).Length } else { 0 } + window_title = $windowTitle + stdout = $stdout + stderr = $stderr + screenshot_path = $ScreenshotPath + profile_root = $ProfileRoot + url = $Url +} + +$result | ConvertTo-Json -Depth 4 -Compress | Set-Content -Path $resultPath -Encoding Ascii +Write-Output ($result | ConvertTo-Json -Depth 4 -Compress) + +if (-not $screenshotReady) { + throw "bare metal image launch did not reach a ready screenshot" +} +'@ + + Set-Content -Path $LaunchScriptPath -Value $launchScript -Encoding Ascii +} + +$browserExists = Test-Path $BrowserExe +if (-not $browserExists) { + throw "bare metal browser binary not found: $BrowserExe" +} + +if (Test-Path $PackageRoot) { + Remove-Item $PackageRoot -Recurse -Force +} + +$bootDir = Join-Path $PackageRoot "boot" +$artifactsDir = Join-Path $PackageRoot "artifacts" +New-Item -ItemType Directory -Force -Path $bootDir, $artifactsDir | Out-Null +Copy-Item -Force $BrowserExe (Join-Path $bootDir "lightpanda.exe") + +$launchScriptPath = Join-Path $PackageRoot "launch.ps1" +New-BareMetalLaunchScript -LaunchScriptPath $launchScriptPath -RepoRoot $RepoRoot + +$manifestPath = Join-Path $PackageRoot "manifest.json" +$gitCommit = $null +try { + $gitCommit = (git -C $RepoRoot rev-parse HEAD).Trim() +} catch { + $gitCommit = $null +} + +$manifest = [ordered]@{ + package_root = $PackageRoot + browser_exe = $BrowserExe + launch_script = $launchScriptPath + boot_binary = (Join-Path $bootDir "lightpanda.exe") + created_utc = (Get-Date).ToUniversalTime().ToString("o") + git_commit = $gitCommit + url = $Url +} +$manifest | ConvertTo-Json -Depth 4 | Set-Content -Path $manifestPath -Encoding Ascii + +$archivePath = Join-Path (Split-Path -Parent (Split-Path -Parent $PackageRoot)) "bare-metal-release.zip" +if (Test-Path $archivePath) { + Remove-Item $archivePath -Force +} +Compress-Archive -Path (Join-Path $PackageRoot "*") -DestinationPath $archivePath -Force + +$launchResult = $null +if ($RunSmoke) { + $launchResult = & $launchScriptPath -Url $Url | ConvertFrom-Json +} + +$result = [ordered]@{ + package_root = $PackageRoot + manifest_path = $manifestPath + launch_script = $launchScriptPath + boot_binary = (Join-Path $bootDir "lightpanda.exe") + archive_path = $archivePath + archive_exists = (Test-Path $archivePath) + archive_size = if (Test-Path $archivePath) { (Get-Item $archivePath).Length } else { 0 } + launch_result = $launchResult +} + +$result | ConvertTo-Json -Depth 6 -Compress diff --git a/src/App.zig b/src/App.zig index 2d930fd6e..7d747938e 100644 --- a/src/App.zig +++ b/src/App.zig @@ -20,10 +20,12 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const log = @import("log.zig"); const Config = @import("Config.zig"); +const HostPaths = @import("HostPaths.zig"); +const Host = @import("sys/host.zig").Host; const Snapshot = @import("browser/js/Snapshot.zig"); const Platform = @import("browser/js/Platform.zig"); +const Display = @import("display/Display.zig"); const Telemetry = @import("telemetry/telemetry.zig").Telemetry; const RobotStore = @import("browser/Robots.zig").RobotStore; @@ -35,21 +37,24 @@ const App = @This(); http: Http, config: *const Config, platform: Platform, +display: Display, snapshot: Snapshot, telemetry: Telemetry, allocator: Allocator, arena_pool: ArenaPool, robots: RobotStore, app_dir_path: ?[]const u8, +host: ?*Host = null, shutdown: bool = false, -pub fn init(allocator: Allocator, config: *const Config) !*App { +pub fn init(allocator: Allocator, config: *const Config, host: ?*Host) !*App { const app = try allocator.create(App); errdefer allocator.destroy(app); app.* = .{ .config = config, .allocator = allocator, + .display = Display.init(allocator, config, host), .robots = RobotStore.init(allocator), .http = undefined, .platform = undefined, @@ -57,10 +62,12 @@ pub fn init(allocator: Allocator, config: *const Config) !*App { .app_dir_path = undefined, .telemetry = undefined, .arena_pool = undefined, + .host = host, }; app.http = try Http.init(allocator, &app.robots, config); errdefer app.http.deinit(); + app.display.setHttpRuntime(&app.http); app.platform = try Platform.init(); errdefer app.platform.deinit(); @@ -68,7 +75,11 @@ pub fn init(allocator: Allocator, config: *const Config) !*App { app.snapshot = try Snapshot.load(); errdefer app.snapshot.deinit(); - app.app_dir_path = getAndMakeAppDir(allocator); + app.app_dir_path = if (host) |host_ref| + host_ref.resolveProfileDir(config.profileDir()) + else + HostPaths.resolveProfileDir(allocator, config.profileDir()); + app.display.setAppDataPath(app.app_dir_path); app.telemetry = try Telemetry.init(app, config.mode); errdefer app.telemetry.deinit(); @@ -93,28 +104,9 @@ pub fn deinit(self: *App) void { self.robots.deinit(); self.http.deinit(); self.snapshot.deinit(); + self.display.deinit(); self.platform.deinit(); self.arena_pool.deinit(); allocator.destroy(self); } - -fn getAndMakeAppDir(allocator: Allocator) ?[]const u8 { - if (@import("builtin").is_test) { - return allocator.dupe(u8, "/tmp") catch unreachable; - } - const app_dir_path = std.fs.getAppDataDir(allocator, "lightpanda") catch |err| { - log.warn(.app, "get data dir", .{ .err = err }); - return null; - }; - - std.fs.cwd().makePath(app_dir_path) catch |err| switch (err) { - error.PathAlreadyExists => return app_dir_path, - else => { - allocator.free(app_dir_path); - log.warn(.app, "create data dir", .{ .err = err, .path = app_dir_path }); - return null; - }, - }; - return app_dir_path; -} diff --git a/src/Config.zig b/src/Config.zig index 5a4cc58e1..919d48e78 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -25,6 +25,7 @@ const dump = @import("browser/dump.zig"); pub const RunMode = enum { help, + browse, fetch, serve, version, @@ -32,6 +33,10 @@ pub const RunMode = enum { }; pub const CDP_MAX_HTTP_REQUEST_SIZE = 4096; +pub const DEFAULT_VIEWPORT_WIDTH: u32 = 1920; +pub const DEFAULT_VIEWPORT_HEIGHT: u32 = 1080; +pub const DEFAULT_HTTP_TIMEOUT_MS: u31 = 5000; +pub const DEFAULT_INTERACTIVE_HTTP_TIMEOUT_MS: u31 = 30000; // max message size // +14 for max websocket payload overhead @@ -60,56 +65,61 @@ pub fn deinit(self: *const Config, allocator: Allocator) void { pub fn tlsVerifyHost(self: *const Config) bool { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.tls_verify_host, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.tls_verify_host, else => unreachable, }; } pub fn obeyRobots(self: *const Config) bool { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.obey_robots, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.obey_robots, else => unreachable, }; } pub fn httpProxy(self: *const Config) ?[:0]const u8 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_proxy, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.http_proxy, else => unreachable, }; } pub fn proxyBearerToken(self: *const Config) ?[:0]const u8 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.proxy_bearer_token, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.proxy_bearer_token, .help, .version => null, }; } pub fn httpMaxConcurrent(self: *const Config) u8 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_max_concurrent orelse 10, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.http_max_concurrent orelse 10, else => unreachable, }; } pub fn httpMaxHostOpen(self: *const Config) u8 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_max_host_open orelse 4, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.http_max_host_open orelse 4, else => unreachable, }; } pub fn httpConnectTimeout(self: *const Config) u31 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_connect_timeout orelse 0, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.http_connect_timeout orelse 0, else => unreachable, }; } pub fn httpTimeout(self: *const Config) u31 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_timeout orelse 5000, + .browse => |opts| opts.common.http_timeout orelse DEFAULT_INTERACTIVE_HTTP_TIMEOUT_MS, + .serve => |opts| opts.common.http_timeout orelse if (opts.common.browser_mode == .headed) + DEFAULT_INTERACTIVE_HTTP_TIMEOUT_MS + else + DEFAULT_HTTP_TIMEOUT_MS, + inline .fetch, .mcp => |opts| opts.common.http_timeout orelse DEFAULT_HTTP_TIMEOUT_MS, else => unreachable, }; } @@ -120,39 +130,67 @@ pub fn httpMaxRedirects(_: *const Config) u8 { pub fn httpMaxResponseSize(self: *const Config) ?usize { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.http_max_response_size, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.http_max_response_size, else => unreachable, }; } pub fn logLevel(self: *const Config) ?log.Level { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.log_level, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.log_level, else => unreachable, }; } pub fn logFormat(self: *const Config) ?log.Format { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.log_format, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.log_format, else => unreachable, }; } pub fn logFilterScopes(self: *const Config) ?[]const log.Scope { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.log_filter_scopes, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.log_filter_scopes, else => unreachable, }; } pub fn userAgentSuffix(self: *const Config) ?[]const u8 { return switch (self.mode) { - inline .serve, .fetch, .mcp => |opts| opts.common.user_agent_suffix, + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.user_agent_suffix, + .help, .version => null, + }; +} + +pub fn profileDir(self: *const Config) ?[]const u8 { + return switch (self.mode) { + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.profile_dir, .help, .version => null, }; } +pub fn browserMode(self: *const Config) BrowserMode { + return switch (self.mode) { + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.browser_mode, + .help, .version => .headless, + }; +} + +pub fn windowWidth(self: *const Config) u32 { + return switch (self.mode) { + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.window_width orelse DEFAULT_VIEWPORT_WIDTH, + .help, .version => DEFAULT_VIEWPORT_WIDTH, + }; +} + +pub fn windowHeight(self: *const Config) u32 { + return switch (self.mode) { + inline .serve, .fetch, .browse, .mcp => |opts| opts.common.window_height orelse DEFAULT_VIEWPORT_HEIGHT, + .help, .version => DEFAULT_VIEWPORT_HEIGHT, + }; +} + pub fn maxConnections(self: *const Config) u16 { return switch (self.mode) { .serve => |opts| opts.cdp_max_connections, @@ -169,12 +207,20 @@ pub fn maxPendingConnections(self: *const Config) u31 { pub const Mode = union(RunMode) { help: bool, // false when being printed because of an error + browse: Browse, fetch: Fetch, serve: Serve, version: void, mcp: Mcp, }; +pub const Browse = struct { + url: [:0]const u8, + common: Common = .{ .browser_mode = .headed }, + screenshot_bmp_path: ?[:0]const u8 = null, + screenshot_png_path: ?[:0]const u8 = null, +}; + pub const Serve = struct { host: []const u8 = "127.0.0.1", port: u16 = 9222, @@ -203,6 +249,11 @@ pub const Fetch = struct { strip: dump.Opts.Strip = .{}, }; +pub const BrowserMode = enum { + headless, + headed, +}; + pub const Common = struct { obey_robots: bool = false, proxy_bearer_token: ?[:0]const u8 = null, @@ -217,6 +268,10 @@ pub const Common = struct { log_format: ?log.Format = null, log_filter_scopes: ?[]log.Scope = null, user_agent_suffix: ?[]const u8 = null, + profile_dir: ?[:0]const u8 = null, + browser_mode: BrowserMode = .headless, + window_width: ?u32 = null, + window_height: ?u32 = null, }; /// Pre-formatted HTTP headers for reuse across Http and Client. @@ -324,13 +379,46 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\--user_agent_suffix \\ Suffix to append to the Lightpanda/X.Y User-Agent \\ + \\--profile_dir Explicit browser profile root for cookies, storage, + \\ downloads, telemetry IDs, and other persisted state. + \\ Defaults to the platform app-data directory when + \\ available. + \\ + \\--browser_mode Browser mode: headless or headed. + \\ Defaults to headless. + \\ + \\--headed Shortcut for '--browser_mode headed' + \\ + \\--headless Shortcut for '--browser_mode headless' + \\ + \\--window_width Window/viewport width in CSS pixels. + \\ Defaults to 1920. + \\ + \\--window_height Window/viewport height in CSS pixels. + \\ Defaults to 1080. + \\ ; // MAX_HELP_LEN| const usage = \\usage: {s} command [options] [URL] \\ - \\Command can be either 'fetch', 'serve', 'mcp' or 'help' + \\Command can be either 'browse', 'fetch', 'serve', 'mcp' or 'help' + \\ + \\browse command + \\Opens the specified URL in a native browser window. + \\Example: {s} browse https://lightpanda.io/ + \\ + \\Options: + \\--screenshot_bmp + \\ Save the first rendered headed browse frame as a BMP file. + \\ Argument must be the output path. + \\ + \\--screenshot_png + \\ Save the first rendered headed browse frame as a PNG file. + \\ Argument must be the output path. + \\ + ++ common_options ++ \\ \\fetch command \\Fetches the specified URL @@ -391,7 +479,7 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\Displays this message \\ ; - std.debug.print(usage, .{ self.exec_name, self.exec_name, self.exec_name, self.exec_name, self.exec_name }); + std.debug.print(usage, .{ self.exec_name, self.exec_name, self.exec_name, self.exec_name, self.exec_name, self.exec_name }); if (success) { return std.process.cleanExit(); } @@ -422,6 +510,8 @@ pub fn parseArgs(allocator: Allocator) !Config { const mode: Mode = switch (run_mode) { .help => .{ .help = true }, + .browse => .{ .browse = parseBrowseArgs(allocator, &args) catch + return init(allocator, exec_name, .{ .help = false }) }, .serve => .{ .serve = parseServeArgs(allocator, &args) catch return init(allocator, exec_name, .{ .help = false }) }, .fetch => .{ .fetch = parseFetchArgs(allocator, &args) catch @@ -466,6 +556,34 @@ fn inferMode(opt: []const u8) ?RunMode { return .serve; } + if (std.mem.eql(u8, opt, "--headed")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--headless")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--browser_mode")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--window_width")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--window_height")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--screenshot_bmp")) { + return .browse; + } + + if (std.mem.eql(u8, opt, "--screenshot_png")) { + return .browse; + } + if (std.mem.eql(u8, opt, "--port")) { return .serve; } @@ -477,6 +595,63 @@ fn inferMode(opt: []const u8) ?RunMode { return null; } +fn parseBrowseArgs( + allocator: Allocator, + args: *std.process.ArgIterator, +) !Browse { + var url: ?[:0]const u8 = null; + var common: Common = .{ .browser_mode = .headed }; + var screenshot_bmp_path: ?[:0]const u8 = null; + var screenshot_png_path: ?[:0]const u8 = null; + + while (args.next()) |opt| { + if (try parseCommonArg(allocator, opt, args, &common)) { + continue; + } + + if (std.mem.eql(u8, "--screenshot_bmp", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--screenshot_bmp" }); + return error.InvalidArgument; + }; + screenshot_bmp_path = try allocator.dupeZ(u8, str); + continue; + } + + if (std.mem.eql(u8, "--screenshot_png", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--screenshot_png" }); + return error.InvalidArgument; + }; + screenshot_png_path = try allocator.dupeZ(u8, str); + continue; + } + + if (std.mem.startsWith(u8, opt, "--")) { + log.fatal(.app, "unknown argument", .{ .mode = "browse", .arg = opt }); + return error.UnkownOption; + } + + if (url != null) { + log.fatal(.app, "duplicate browse url", .{ .help = "only 1 URL can be specified" }); + return error.TooManyURLs; + } + url = try allocator.dupeZ(u8, opt); + } + + if (url == null) { + log.fatal(.app, "missing browse url", .{ .help = "URL to browse must be provided" }); + return error.MissingURL; + } + + return .{ + .url = url.?, + .common = common, + .screenshot_bmp_path = screenshot_bmp_path, + .screenshot_png_path = screenshot_png_path, + }; +} + fn parseServeArgs( allocator: Allocator, args: *std.process.ArgIterator, @@ -845,5 +1020,113 @@ fn parseCommonArg( return true; } + if (std.mem.eql(u8, "--profile_dir", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--profile_dir" }); + return error.InvalidArgument; + }; + common.profile_dir = try allocator.dupeZ(u8, str); + return true; + } + + if (std.mem.eql(u8, "--browser_mode", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--browser_mode" }); + return error.InvalidArgument; + }; + + common.browser_mode = std.meta.stringToEnum(BrowserMode, str) orelse { + log.fatal(.app, "invalid option choice", .{ .arg = "--browser_mode", .value = str }); + return error.InvalidArgument; + }; + return true; + } + + if (std.mem.eql(u8, "--headed", opt)) { + common.browser_mode = .headed; + return true; + } + + if (std.mem.eql(u8, "--headless", opt)) { + common.browser_mode = .headless; + return true; + } + + if (std.mem.eql(u8, "--window_width", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--window_width" }); + return error.InvalidArgument; + }; + + common.window_width = std.fmt.parseInt(u32, str, 10) catch |err| { + log.fatal(.app, "invalid argument value", .{ .arg = "--window_width", .err = err }); + return error.InvalidArgument; + }; + if (common.window_width.? == 0) { + log.fatal(.app, "invalid argument value", .{ .arg = "--window_width", .value = str }); + return error.InvalidArgument; + } + return true; + } + + if (std.mem.eql(u8, "--window_height", opt)) { + const str = args.next() orelse { + log.fatal(.app, "missing argument value", .{ .arg = "--window_height" }); + return error.InvalidArgument; + }; + + common.window_height = std.fmt.parseInt(u32, str, 10) catch |err| { + log.fatal(.app, "invalid argument value", .{ .arg = "--window_height", .err = err }); + return error.InvalidArgument; + }; + if (common.window_height.? == 0) { + log.fatal(.app, "invalid argument value", .{ .arg = "--window_height", .value = str }); + return error.InvalidArgument; + } + return true; + } + return false; } + +test "browse defaults to interactive http timeout" { + var config = try Config.init(std.testing.allocator, "test", .{ + .browse = .{ .url = "https://example.com/" }, + }); + defer config.deinit(std.testing.allocator); + + try std.testing.expectEqual(DEFAULT_INTERACTIVE_HTTP_TIMEOUT_MS, config.httpTimeout()); +} + +test "headed serve defaults to interactive http timeout" { + var config = try Config.init(std.testing.allocator, "test", .{ + .serve = .{ .common = .{ .browser_mode = .headed } }, + }); + defer config.deinit(std.testing.allocator); + + try std.testing.expectEqual(DEFAULT_INTERACTIVE_HTTP_TIMEOUT_MS, config.httpTimeout()); +} + +test "headless serve keeps shorter default http timeout" { + var config = try Config.init(std.testing.allocator, "test", .{ + .serve = .{}, + }); + defer config.deinit(std.testing.allocator); + + try std.testing.expectEqual(DEFAULT_HTTP_TIMEOUT_MS, config.httpTimeout()); +} + +test "explicit http timeout overrides interactive defaults" { + var config = try Config.init(std.testing.allocator, "test", .{ + .browse = .{ + .url = "https://example.com/", + .common = .{ + .browser_mode = .headed, + .http_timeout = 1234, + }, + }, + }); + defer config.deinit(std.testing.allocator); + + try std.testing.expectEqual(@as(u31, 1234), config.httpTimeout()); +} diff --git a/src/HostPaths.zig b/src/HostPaths.zig new file mode 100644 index 000000000..2deae2215 --- /dev/null +++ b/src/HostPaths.zig @@ -0,0 +1,152 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const builtin = @import("builtin"); + +const Allocator = std.mem.Allocator; + +const log = @import("log.zig"); + +pub fn resolveProfileDir(allocator: Allocator, override_path: ?[]const u8) ?[]const u8 { + if (override_path) |path| { + return copyAndPrepareDir(allocator, path) catch |err| { + log.warn(.app, "use explicit profile dir", .{ .err = err, .path = path }); + return null; + }; + } + + if (builtin.is_test) { + return allocator.dupe(u8, "/tmp") catch unreachable; + } + + if (!supportsProfileDirFilesystem()) { + return null; + } + + const app_dir_path = std.fs.getAppDataDir(allocator, "lightpanda") catch |err| { + log.warn(.app, "get data dir", .{ .err = err }); + return null; + }; + + std.fs.cwd().makePath(app_dir_path) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => { + allocator.free(app_dir_path); + log.warn(.app, "create data dir", .{ .err = err, .path = app_dir_path }); + return null; + }, + }; + return app_dir_path; +} + +fn copyAndPrepareDir(allocator: Allocator, path: []const u8) ![]const u8 { + const owned = try allocator.dupe(u8, path); + errdefer allocator.free(owned); + if (supportsProfileDirFilesystem()) { + std.fs.cwd().makePath(owned) catch |err| switch (err) { + error.PathAlreadyExists => return owned, + else => return err, + }; + } + return owned; +} + +pub fn resolveProfileFile(allocator: Allocator, profile_root: ?[]const u8, name: []const u8) ?[]u8 { + const root = profile_root orelse return null; + return std.fs.path.join(allocator, &.{ root, name }) catch null; +} + +pub fn resolveProfileSubdir(allocator: Allocator, profile_root: ?[]const u8, subdir: []const u8) ?[]u8 { + const path = resolveProfileFile(allocator, profile_root, subdir) orelse return null; + if (!supportsProfileDirFilesystem()) { + return path; + } + + std.fs.cwd().makePath(path) catch |err| switch (err) { + error.PathAlreadyExists => return path, + else => { + log.warn(.app, "create profile subdir", .{ .err = err, .path = path }); + allocator.free(path); + return null; + }, + }; + return path; +} + +fn supportsProfileDirFilesystem() bool { + return switch (builtin.os.tag) { + .windows, .linux, .macos, .ios, .tvos, .watchos, .visionos, .freebsd, .netbsd, .openbsd, .dragonfly, .haiku => true, + else => false, + }; +} + +test "resolveProfileDir uses explicit override" { + const rel_dir = "tmp-host-profile-dir"; + std.fs.cwd().deleteTree(rel_dir) catch {}; + defer std.fs.cwd().deleteTree(rel_dir) catch {}; + + const resolved = resolveProfileDir(std.testing.allocator, rel_dir).?; + defer std.testing.allocator.free(resolved); + + try std.testing.expectEqualStrings(rel_dir, resolved); + var dir = try std.fs.cwd().openDir(rel_dir, .{}); + defer dir.close(); +} + +test "resolveProfileFile joins profile root and file name" { + const rel_dir = "tmp-host-profile-file"; + std.fs.cwd().deleteTree(rel_dir) catch {}; + defer std.fs.cwd().deleteTree(rel_dir) catch {}; + + const root = resolveProfileDir(std.testing.allocator, rel_dir).?; + defer std.testing.allocator.free(root); + + const resolved = resolveProfileFile(std.testing.allocator, root, "cookies-v1.txt") orelse return error.TestExpected; + defer std.testing.allocator.free(resolved); + + const expected = try std.fs.path.join(std.testing.allocator, &.{ root, "cookies-v1.txt" }); + defer std.testing.allocator.free(expected); + + try std.testing.expectEqualStrings(expected, resolved); +} + +test "resolveProfileSubdir creates profile subdir" { + if (!supportsProfileDirFilesystem()) return; + + const rel_dir = "tmp-host-profile-subdir"; + std.fs.cwd().deleteTree(rel_dir) catch {}; + defer std.fs.cwd().deleteTree(rel_dir) catch {}; + + const root = resolveProfileDir(std.testing.allocator, rel_dir).?; + defer std.testing.allocator.free(root); + + const subdir = resolveProfileSubdir(std.testing.allocator, root, "downloads") orelse return error.TestExpected; + defer std.testing.allocator.free(subdir); + + const expected = try std.fs.path.join(std.testing.allocator, &.{ root, "downloads" }); + defer std.testing.allocator.free(expected); + + try std.testing.expect(std.mem.endsWith(u8, subdir, expected)); + + var dir = if (std.fs.path.isAbsolute(subdir)) + try std.fs.openDirAbsolute(subdir, .{}) + else + try std.fs.cwd().openDir(subdir, .{}); + defer dir.close(); +} diff --git a/src/Net.zig b/src/Net.zig index bd723be69..0d330f6b9 100644 --- a/src/Net.zig +++ b/src/Net.zig @@ -29,6 +29,8 @@ const assert = @import("lightpanda").assert; pub const ENABLE_DEBUG = false; const IS_DEBUG = builtin.mode == .Debug; +pub const DISABLED_PROXY: [:0]const u8 = ""; +const LOOPBACK_NO_PROXY: [:0]const u8 = "localhost,127.0.0.1,::1,[::1]"; pub const Blob = libcurl.CurlBlob; pub const WaitFd = libcurl.CurlWaitFd; @@ -235,7 +237,10 @@ pub const ResponseHead = struct { }; pub fn globalInit() Error!void { - try libcurl.curl_global_init(.{ .ssl = true }); + try libcurl.curl_global_init(.{ + .ssl = true, + .win32 = builtin.os.tag == .windows, + }); } pub fn globalDeinit() void { @@ -266,7 +271,12 @@ pub const Connection = struct { const http_proxy = config.httpProxy(); if (http_proxy) |proxy| { try libcurl.curl_easy_setopt(easy, .proxy, proxy.ptr); + } else { + // Keep libcurl from inheriting ambient system/environment proxy settings + // unless the user explicitly configured one for Lightpanda. + try libcurl.curl_easy_setopt(easy, .proxy, DISABLED_PROXY.ptr); } + try libcurl.curl_easy_setopt(easy, .no_proxy, LOOPBACK_NO_PROXY.ptr); // tls if (ca_blob_) |ca_blob| { @@ -1106,9 +1116,14 @@ pub const WsConnection = struct { timeout_ms: u32, pub fn init(socket: posix.socket_t, allocator: Allocator, json_version_response: []const u8, timeout_ms: u32) !WsConnection { - const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0); - const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true })); - assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{}); + const socket_flags = if (comptime builtin.os.tag == .windows) + 0 + else + try posix.fcntl(socket, posix.F.GETFL, 0); + if (comptime builtin.os.tag != .windows) { + const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true })); + assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{}); + } var reader = try Reader(true).init(allocator); errdefer reader.deinit(); @@ -1133,7 +1148,7 @@ pub const WsConnection = struct { var changed_to_blocking: bool = false; defer _ = self.send_arena.reset(.{ .retain_with_limit = 1024 * 32 }); - defer if (changed_to_blocking) { + defer if (changed_to_blocking and comptime builtin.os.tag != .windows) { // We had to change our socket to blocking me to get our write out // We need to change it back to non-blocking. _ = posix.fcntl(self.socket, posix.F.SETFL, self.socket_flags) catch |err| { @@ -1144,6 +1159,11 @@ pub const WsConnection = struct { LOOP: while (pos < data.len) { const written = posix.write(self.socket, data[pos..]) catch |err| switch (err) { error.WouldBlock => { + if (comptime builtin.os.tag == .windows) { + std.Thread.sleep(100 * std.time.ns_per_us); + continue :LOOP; + } + // self.socket is nonblocking, because we don't want to block // reads. But our life is a lot easier if we block writes, // largely, because we don't have to maintain a queue of pending @@ -1373,6 +1393,10 @@ pub const WsConnection = struct { } pub fn setBlocking(self: *WsConnection, blocking: bool) !void { + if (comptime builtin.os.tag == .windows) { + return; + } + if (blocking) { _ = try posix.fcntl(self.socket, posix.F.SETFL, self.socket_flags & ~@as(u32, @bitCast(posix.O{ .NONBLOCK = true }))); } else { diff --git a/src/Server.zig b/src/Server.zig index bd9905602..093a78c82 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -84,7 +84,7 @@ pub fn stop(self: *Server) void { .linux => posix.shutdown(listener, .recv) catch |err| { log.warn(.app, "listener shutdown", .{ .err = err }); }, - .macos, .freebsd, .netbsd, .openbsd => { + .windows, .macos, .freebsd, .netbsd, .openbsd => { self.listener = null; posix.close(listener); }, diff --git a/src/Sighandler.zig b/src/Sighandler.zig index 2b2d7f294..bdfac1dc8 100644 --- a/src/Sighandler.zig +++ b/src/Sighandler.zig @@ -22,6 +22,7 @@ //! The structure does not clear the memory allocated in the arena, //! clear the entire arena when exiting the program. const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const lp = @import("lightpanda"); @@ -44,11 +45,17 @@ pub const Listener = struct { }; pub fn install(self: *SigHandler) !void { + if (comptime builtin.os.tag == .windows) { + return; + } + // Block SIGINT and SIGTERM for the current thread and all created from it self.sigset = std.posix.sigemptyset(); std.posix.sigaddset(&self.sigset, std.posix.SIG.INT); std.posix.sigaddset(&self.sigset, std.posix.SIG.TERM); - std.posix.sigaddset(&self.sigset, std.posix.SIG.QUIT); + if (comptime builtin.os.tag != .windows) { + std.posix.sigaddset(&self.sigset, std.posix.SIG.QUIT); + } std.posix.sigprocmask(std.posix.SIG.BLOCK, &self.sigset, null); self.handle_thread = try std.Thread.spawn(.{ .allocator = self.arena }, SigHandler.sighandle, .{self}); diff --git a/src/TestHTTPServer.zig b/src/TestHTTPServer.zig index 21d9fa784..b5defb880 100644 --- a/src/TestHTTPServer.zig +++ b/src/TestHTTPServer.zig @@ -60,7 +60,10 @@ pub fn run(self: *TestHTTPServer, wg: *std.Thread.WaitGroup) !void { while (true) { const conn = listener.accept() catch |err| { - if (self.shutdown.load(.acquire) or err == error.SocketNotListening) { + if ((@import("builtin").target.os.tag == .windows and err == error.Unexpected) or + self.shutdown.load(.acquire) or + err == error.SocketNotListening) + { return; } return err; @@ -78,23 +81,18 @@ fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !voi var conn_writer = conn.stream.writer(&req_buf); var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface); + var req = http_server.receiveHead() catch |err| switch (err) { + error.ReadFailed, error.HttpConnectionClosing => return, + else => { + std.debug.print("Test HTTP Server error: {}\n", .{err}); + return err; + }, + }; - while (true) { - var req = http_server.receiveHead() catch |err| switch (err) { - error.ReadFailed => continue, - error.HttpConnectionClosing => continue, - else => { - std.debug.print("Test HTTP Server error: {}\n", .{err}); - return err; - }, - }; - - self.handler(&req) catch |err| { - std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err }); - try req.respond("server error", .{ .status = .internal_server_error }); - return; - }; - } + self.handler(&req) catch |err| { + std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err }); + try req.respond("server error", .{ .status = .internal_server_error }); + }; } pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void { @@ -131,6 +129,46 @@ fn getContentType(file_path: []const u8) []const u8 { return "application/json"; } + if (std.mem.endsWith(u8, file_path, ".css")) { + return "text/css"; + } + + if (std.mem.endsWith(u8, file_path, ".png")) { + return "image/png"; + } + + if (std.mem.endsWith(u8, file_path, ".jpg") or std.mem.endsWith(u8, file_path, ".jpeg")) { + return "image/jpeg"; + } + + if (std.mem.endsWith(u8, file_path, ".gif")) { + return "image/gif"; + } + + if (std.mem.endsWith(u8, file_path, ".webp")) { + return "image/webp"; + } + + if (std.mem.endsWith(u8, file_path, ".svg")) { + return "image/svg+xml"; + } + + if (std.mem.endsWith(u8, file_path, ".woff2")) { + return "font/woff2"; + } + + if (std.mem.endsWith(u8, file_path, ".woff")) { + return "font/woff"; + } + + if (std.mem.endsWith(u8, file_path, ".ttf")) { + return "font/ttf"; + } + + if (std.mem.endsWith(u8, file_path, ".otf")) { + return "font/otf"; + } + if (std.mem.endsWith(u8, file_path, ".html")) { return "text/html"; } diff --git a/src/browser/Browser.zig b/src/browser/Browser.zig index 503306d35..63632b4e7 100644 --- a/src/browser/Browser.zig +++ b/src/browser/Browser.zig @@ -25,8 +25,11 @@ const js = @import("js/js.zig"); const log = @import("../log.zig"); const App = @import("../App.zig"); const HttpClient = @import("../http/Client.zig"); +const storage = @import("webapi/storage/storage.zig"); +const indexed_db = @import("webapi/storage/indexed_db.zig"); const ArenaPool = App.ArenaPool; +const CookieJar = @import("webapi/storage/Cookie.zig").Jar; const IS_DEBUG = @import("builtin").mode == .Debug; @@ -44,10 +47,17 @@ session: ?Session, allocator: Allocator, arena_pool: *ArenaPool, http_client: *HttpClient, +allow_script_popups: bool = true, +shared_cookie_jar: ?*CookieJar = null, +shared_storage_shed: ?*storage.Shed = null, +shared_indexed_db_shed: ?*indexed_db.Shed = null, const InitOpts = struct { env: js.Env.InitOpts = .{}, http_client: *HttpClient, + shared_cookie_jar: ?*CookieJar = null, + shared_storage_shed: ?*storage.Shed = null, + shared_indexed_db_shed: ?*indexed_db.Shed = null, }; pub fn init(app: *App, opts: InitOpts) !Browser { @@ -63,6 +73,10 @@ pub fn init(app: *App, opts: InitOpts) !Browser { .allocator = allocator, .arena_pool = &app.arena_pool, .http_client = opts.http_client, + .allow_script_popups = true, + .shared_cookie_jar = opts.shared_cookie_jar, + .shared_storage_shed = opts.shared_storage_shed, + .shared_indexed_db_shed = opts.shared_indexed_db_shed, }; } @@ -88,11 +102,15 @@ pub fn closeSession(self: *Browser) void { } pub fn runMicrotasks(self: *Browser) void { + self.env.isolate.enter(); + defer self.env.isolate.exit(); self.env.runMicrotasks(); } pub fn runMacrotasks(self: *Browser) !?u64 { const env = &self.env; + env.isolate.enter(); + defer env.isolate.exit(); const time_to_next = try self.env.runMacrotasks(); env.pumpMessageLoop(); @@ -104,12 +122,18 @@ pub fn runMacrotasks(self: *Browser) !?u64 { } pub fn hasBackgroundTasks(self: *Browser) bool { + self.env.isolate.enter(); + defer self.env.isolate.exit(); return self.env.hasBackgroundTasks(); } pub fn waitForBackgroundTasks(self: *Browser) void { + self.env.isolate.enter(); + defer self.env.isolate.exit(); self.env.waitForBackgroundTasks(); } pub fn runIdleTasks(self: *const Browser) void { + self.env.isolate.enter(); + defer self.env.isolate.exit(); self.env.runIdleTasks(); } diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 17271635b..b349471f3 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -21,6 +21,7 @@ const builtin = @import("builtin"); const log = @import("../log.zig"); const String = @import("../string.zig").String; +const testing = @import("../testing.zig"); const js = @import("js/js.zig"); const Page = @import("Page.zig"); @@ -227,6 +228,30 @@ const DispatchDirectOptions = struct { inject_target: bool = true, }; +fn isRecoverableDispatchError(err: anyerror) bool { + const DOMException = @import("webapi/DOMException.zig"); + if (DOMException.fromError(err) != null) { + return true; + } + + return switch (err) { + error.JSExecCallback, + error.CompilationError, + error.ExecutionError, + error.JsException, + => true, + else => false, + }; +} + +fn swallowDispatchError(comptime context: []const u8, err: anyerror) void { + if (!isRecoverableDispatchError(err)) { + log.warn(.event, context, .{ .err = err }); + return; + } + log.warn(.event, context, .{ .err = err }); +} + // Direct dispatch for non-DOM targets (Window, XHR, AbortSignal) or DOM nodes with // property handlers. No propagation - just calls the handler and registered listeners. // Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function @@ -259,8 +284,7 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, if (func.callWithThis(void, target, .{event})) { was_dispatched = true; } else |err| { - // a non-JS error - log.warn(.event, opts.context, .{ .err = err }); + swallowDispatchError(opts.context, err); } } @@ -328,15 +352,21 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, event._current_target = target; switch (listener.function) { - .value => |value| try ls.toLocal(value).callWithThis(void, target, .{event}), + .value => |value| ls.toLocal(value).callWithThis(void, target, .{event}) catch |err| { + swallowDispatchError(opts.context, err); + }, .string => |string| { const str = try page.call_arena.dupeZ(u8, string.str()); - try ls.local.eval(str, null); + ls.local.eval(str, null) catch |err| { + swallowDispatchError(opts.context, err); + }; }, .object => |obj_global| { const obj = ls.toLocal(obj_global); if (try obj.getFunction("handleEvent")) |handleEvent| { - try handleEvent.callWithThis(void, obj, .{event}); + handleEvent.callWithThis(void, obj, .{event}) catch |err| { + swallowDispatchError(opts.context, err); + }; } }, } @@ -405,6 +435,10 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts // Execute default action if not prevented if (event._prevent_default) { // can't return in a defer (╯°□°)╯︵ ┻━┻ + } else if (event._type_string.eql(comptime .wrap("mousedown"))) { + page.handleMouseDown(target) catch |err| { + log.warn(.event, "page.mousedown", .{ .err = err }); + }; } else if (event._type_string.eql(comptime .wrap("click"))) { page.handleClick(target) catch |err| { log.warn(.event, "page.click", .{ .err = err }); @@ -481,7 +515,9 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts was_handled = true; event._current_target = target_et; - try ls.toLocal(inline_handler).callWithThis(void, target_et, .{event}); + ls.toLocal(inline_handler).callWithThis(void, target_et, .{event}) catch |err| { + swallowDispatchError("dispatch.inline", err); + }; if (event._stop_propagation) { return; @@ -609,15 +645,21 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe } switch (listener.function) { - .value => |value| try local.toLocal(value).callWithThis(void, current_target, .{event}), + .value => |value| local.toLocal(value).callWithThis(void, current_target, .{event}) catch |err| { + swallowDispatchError("dispatchPhase", err); + }, .string => |string| { const str = try page.call_arena.dupeZ(u8, string.str()); - try local.eval(str, null); + local.eval(str, null) catch |err| { + swallowDispatchError("dispatchPhase", err); + }; }, .object => |obj_global| { const obj = local.toLocal(obj_global); if (try obj.getFunction("handleEvent")) |handleEvent| { - try handleEvent.callWithThis(void, obj, .{event}); + handleEvent.callWithThis(void, obj, .{event}) catch |err| { + swallowDispatchError("dispatchPhase", err); + }; } }, } @@ -905,3 +947,23 @@ const ActivationState = struct { try page._event_manager.dispatch(target, event); } }; + +test "dispatch contains selector syntax errors inside load listeners" { + var page = try testing.pageTest("page/selector_error_containment.html"); + defer page._session.removePage(); + + _ = page._session.wait(250); + + const title = (try page.getTitle()) orelse return error.MissingTitle; + try std.testing.expectEqualStrings("Selector Error Survived", title); +} + +test "dispatch contains selector syntax errors inside promise microtasks" { + var page = try testing.pageTest("page/selector_error_microtask_containment.html"); + defer page._session.removePage(); + + _ = page._session.wait(250); + + const title = (try page.getTitle()) orelse return error.MissingTitle; + try std.testing.expectEqualStrings("Selector Microtask Survived", title); +} diff --git a/src/browser/Page.zig b/src/browser/Page.zig index dedb2a6e9..56d23d6da 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -33,6 +33,7 @@ const String = @import("../string.zig").String; const Mime = @import("Mime.zig"); const Factory = @import("Factory.zig"); const Session = @import("Session.zig"); +const PopupSource = @import("PopupSource.zig").PopupSource; const EventManager = @import("EventManager.zig"); const ScriptManager = @import("ScriptManager.zig"); @@ -61,6 +62,7 @@ const storage = @import("webapi/storage/storage.zig"); const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig"); const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind; const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig"); +const DocumentPainter = @import("../render/DocumentPainter.zig"); const Http = App.Http; const Net = @import("../Net.zig"); @@ -112,6 +114,8 @@ _element_shadow_roots: Element.ShadowRootLookup = .empty, _node_owner_documents: Node.OwnerDocumentLookup = .empty, _element_assigned_slots: Element.AssignedSlotLookup = .empty, _element_scroll_positions: Element.ScrollPositionLookup = .empty, +_element_scroll_metrics: Element.ScrollMetricsLookup = .empty, +_element_layout_boxes: Element.LayoutBoxLookup = .empty, _element_namespace_uris: Element.NamespaceUriLookup = .empty, /// Lazily-created inline event listeners (or listeners provided as attributes). @@ -230,6 +234,7 @@ frames_sorted: bool = true, // DOM version used to invalidate cached state of "live" collections version: usize = 0, +presentation_version: usize = 0, // This is maybe not great. It's a counter on the number of events that we're // waiting on before triggering the "load" event. Essentially, we need all @@ -242,6 +247,7 @@ _parent_notified: bool = false, _type: enum { root, frame }, // only used for logs right now _req_id: u32 = 0, _navigated_options: ?NavigatedOpts = null, +_keyboard_text_suppression_depth: u32 = 0, pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void { if (comptime IS_DEBUG) { @@ -285,12 +291,19 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void screen = p.window._screen; visual_viewport = p.window._visual_viewport; } else { + const viewport = session.browser.app.display.viewport; screen = try factory.eventTarget(Screen{ ._proto = undefined, ._orientation = null, + ._width = viewport.width, + ._height = viewport.height, + ._avail_height = viewport.availHeight(), }); visual_viewport = try factory.eventTarget(VisualViewport{ ._proto = undefined, + ._width = viewport.width, + ._height = viewport.height, + ._scale = viewport.device_pixel_ratio, }); } @@ -303,6 +316,7 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void ._screen = screen, ._visual_viewport = visual_viewport, }); + try self.window.syncStorageBucket(); self._script_manager = ScriptManager.init(browser.allocator, browser.http_client, self); errdefer self._script_manager.deinit(); @@ -344,19 +358,20 @@ pub fn deinit(self: *Page, abort_http: bool) void { } const session = self._session; + self.window.unregisterStorageBucket(); session.browser.env.destroyContext(self.js); self._script_manager.shutdown = true; - - if (self.parent == null) { - session.browser.http_client.abort(); - } else if (abort_http) { - // a small optimization, it's faster to abort _everything_ on the root - // page, so we prefer that. But if it's just the frame that's going - // away (a frame navigation) then we'll abort the frame-related requests - session.browser.http_client.abortFrame(self._frame_id); + if (abort_http) { + if (self.parent == null) { + session.browser.http_client.abort(); + } else { + // a small optimization, it's faster to abort _everything_ on the root + // page, so we prefer that. But if it's just the frame that's going + // away (a frame navigation) then we'll abort the frame-related requests + session.browser.http_client.abortFrame(self._frame_id); + } } - self._script_manager.deinit(); if (comptime IS_DEBUG) { @@ -378,6 +393,18 @@ pub fn deinit(self: *Page, abort_http: bool) void { } } +pub fn setSuspended(self: *Page, suspended: bool) void { + self.js.suspended = suspended; +} + +pub fn setViewport(self: *Page, width: u32, height: u32, device_pixel_ratio: f64) !void { + self.window._screen.setDimensions(width, height); + self.window._visual_viewport.setMetrics(width, height, device_pixel_ratio); + + const resize_event = try Event.initTrusted(comptime .wrap("resize"), .{}, self); + try self._event_manager.dispatch(self.window.asEventTarget(), resize_event); +} + pub fn base(self: *const Page) [:0]const u8 { return self.base_url orelse self.url; } @@ -397,14 +424,47 @@ pub fn getOrigin(self: *Page, allocator: Allocator) !?[]const u8 { // * cookies // * referer pub fn headersForRequest(self: *Page, temp: Allocator, url: [:0]const u8, headers: *Http.Headers) !void { - try self.requestCookie(.{}).headersForRequest(temp, url, headers); + return self.headersForRequestWithPolicy(temp, url, headers, .{}); +} + +pub const RequestHeaderPolicy = struct { + include_credentials: bool = true, + referer_override_url: ?[]const u8 = null, + authorization_source_url: ?[:0]const u8 = null, +}; + +pub fn headersForRequestWithPolicy( + self: *Page, + temp: Allocator, + url: [:0]const u8, + headers: *Http.Headers, + policy: RequestHeaderPolicy, +) !void { + if (policy.include_credentials) { + try self.requestCookie(.{}).headersForRequest(temp, url, headers); + const authorization_url = policy.authorization_source_url orelse url; + if (try authorizationHeaderValueForRequest(temp, self.url, authorization_url)) |authorization_value| { + const authorization_header = try std.fmt.allocPrintSentinel(temp, "Authorization: {s}", .{authorization_value}, 0); + try headers.add(authorization_header); + } + } // Build the referer const referer = blk: { + if (policy.referer_override_url) |override_url| { + if (!std.mem.startsWith(u8, override_url, "http")) { + break :blk ""; + } + const override_url_z = try temp.dupeZ(u8, override_url); + const referer_value = try refererValueForUrl(temp, override_url_z); + break :blk try std.mem.concatWithSentinel(temp, u8, &.{ "Referer: ", referer_value }, 0); + } + if (self.referer_header == null) { // build the cache if (std.mem.startsWith(u8, self.url, "http")) { - self.referer_header = try std.mem.concatWithSentinel(self.arena, u8, &.{ "Referer: ", self.url }, 0); + const referer_value = try refererValueForUrl(self.arena, self.url); + self.referer_header = try std.mem.concatWithSentinel(self.arena, u8, &.{ "Referer: ", referer_value }, 0); } else { self.referer_header = ""; } @@ -419,6 +479,63 @@ pub fn headersForRequest(self: *Page, temp: Allocator, url: [:0]const u8, header } } +fn authorizationHeaderValueForRequest(temp: Allocator, page_url: [:0]const u8, request_url: [:0]const u8) !?[]const u8 { + if (try authorizationHeaderValueForUrl(temp, request_url)) |authorization_value| { + return authorization_value; + } + + if (URL.getUsername(page_url).len == 0) { + return null; + } + + if (!(try urlsShareOrigin(temp, page_url, request_url))) { + return null; + } + + return authorizationHeaderValueForUrl(temp, page_url); +} + +fn authorizationHeaderValueForUrl(temp: Allocator, url: [:0]const u8) !?[]const u8 { + const username_raw = URL.getUsername(url); + if (username_raw.len == 0) { + return null; + } + + var arena_instance = std.heap.ArenaAllocator.init(temp); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const password_raw = URL.getPassword(url); + const username = try URL.unescape(arena, username_raw); + const password = try URL.unescape(arena, password_raw); + const userpwd = try std.fmt.allocPrint(arena, "{s}:{s}", .{ username, password }); + + const encoder = std.base64.standard.Encoder; + const out_len = encoder.calcSize(userpwd.len); + const out = try arena.alloc(u8, out_len); + _ = encoder.encode(out, userpwd); + return try std.fmt.allocPrint(temp, "Basic {s}", .{out}); +} + +fn urlsShareOrigin(temp: Allocator, first: [:0]const u8, second: [:0]const u8) !bool { + const first_origin = try URL.getOrigin(temp, first) orelse return false; + const second_origin = try URL.getOrigin(temp, second) orelse return false; + return std.mem.eql(u8, first_origin, second_origin); +} + +fn refererValueForUrl(allocator: Allocator, url: [:0]const u8) ![]const u8 { + if (!std.mem.startsWith(u8, url, "http://") and !std.mem.startsWith(u8, url, "https://")) { + return ""; + } + + const protocol = URL.getProtocol(url); + const host = URL.getHost(url); + const pathname = URL.getPathname(url); + const search = URL.getSearch(url); + const referer = try URL.buildUrl(allocator, protocol, host, pathname, search, ""); + return referer; +} + const GetArenaOpts = struct { debug: []const u8, }; @@ -451,7 +568,8 @@ pub fn releaseArena(self: *Page, allocator: Allocator) void { pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool { const current_origin = (try URL.getOrigin(self.call_arena, self.url)) orelse return false; - return std.mem.startsWith(u8, url, current_origin); + const target_origin = (try URL.getOrigin(self.call_arena, url)) orelse return false; + return std.mem.eql(u8, current_origin, target_origin); } pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void { @@ -558,7 +676,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi .method = opts.method, .headers = headers, .body = opts.body, - .cookie_jar = &session.cookie_jar, + .cookie_jar = session.cookie_jar, .resource_type = .document, .notification = self._session.notification, .header_callback = pageHeaderDoneCallback, @@ -571,6 +689,23 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi }; } +pub fn navigateOwned(self: *Page, request_url: []const u8, opts: NavigateOpts) !void { + const owned_url = try self.arena.dupeZ(u8, request_url); + const owned_body = if (opts.body) |body| + try self.arena.dupe(u8, body) + else + null; + const owned_header = if (opts.header) |header| + try self.arena.dupeZ(u8, header) + else + null; + + var owned_opts = opts; + owned_opts.body = owned_body; + owned_opts.header = owned_header; + return self.navigate(owned_url, owned_opts); +} + // Navigation can happen in many places, such as executing a @@ -89,6 +91,350 @@ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/css/font_face_loading.css b/src/browser/tests/css/font_face_loading.css new file mode 100644 index 000000000..64bd8b920 --- /dev/null +++ b/src/browser/tests/css/font_face_loading.css @@ -0,0 +1,8 @@ +@font-face { + font-family: "Runner Font"; + src: url("font_face_test.woff2"); +} + +.font-face-target { + font-family: "Runner Font", sans-serif; +} diff --git a/src/browser/tests/css/font_face_set.html b/src/browser/tests/css/font_face_set.html index a860669ea..0f996c450 100644 --- a/src/browser/tests/css/font_face_set.html +++ b/src/browser/tests/css/font_face_set.html @@ -56,3 +56,40 @@ testing.expectEqual('FontFaceSet', document.fonts.constructor.name); } + + diff --git a/src/browser/tests/css/font_face_test.woff b/src/browser/tests/css/font_face_test.woff new file mode 100644 index 000000000..fe72de7f3 Binary files /dev/null and b/src/browser/tests/css/font_face_test.woff differ diff --git a/src/browser/tests/css/font_face_test.woff2 b/src/browser/tests/css/font_face_test.woff2 new file mode 100644 index 000000000..89713fa9a Binary files /dev/null and b/src/browser/tests/css/font_face_test.woff2 differ diff --git a/src/browser/tests/css/private_font_test.ttf b/src/browser/tests/css/private_font_test.ttf new file mode 100644 index 000000000..2ecf01bde Binary files /dev/null and b/src/browser/tests/css/private_font_test.ttf differ diff --git a/src/browser/tests/css/stylesheet.html b/src/browser/tests/css/stylesheet.html index 59f04d47a..5625e853a 100644 --- a/src/browser/tests/css/stylesheet.html +++ b/src/browser/tests/css/stylesheet.html @@ -1,4 +1,5 @@ + + + + + + + + + + + @@ -275,3 +357,56 @@ testing.expectEqual('red', div.style.getPropertyValue('color')); } + + + + diff --git a/src/browser/tests/css/stylesheet_imported.css b/src/browser/tests/css/stylesheet_imported.css new file mode 100644 index 000000000..dcf86f19e --- /dev/null +++ b/src/browser/tests/css/stylesheet_imported.css @@ -0,0 +1 @@ +body { background-color: rgb(10, 110, 210); } diff --git a/src/browser/tests/document/collections.html b/src/browser/tests/document/collections.html index b1c575858..4f9976ba6 100644 --- a/src/browser/tests/document/collections.html +++ b/src/browser/tests/document/collections.html @@ -1,7 +1,9 @@ -
+
+ +
@@ -22,3 +24,8 @@ testing.expectEqual($('#link1'), document.links[0]); testing.expectEqual($('#link2'), document.links[1]); + + diff --git a/src/browser/tests/document/element_from_point.html b/src/browser/tests/document/element_from_point.html index 0ee07deba..38e435dc8 100644 --- a/src/browser/tests/document/element_from_point.html +++ b/src/browser/tests/document/element_from_point.html @@ -8,6 +8,10 @@
Child
+ + + + - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/element/html/button.html b/src/browser/tests/element/html/button.html index 76e5be8bd..3dcf8ed3b 100644 --- a/src/browser/tests/element/html/button.html +++ b/src/browser/tests/element/html/button.html @@ -126,3 +126,35 @@ testing.expectFalse(button.outerHTML.includes('required')) } + + + + diff --git a/src/browser/tests/element/html/form.html b/src/browser/tests/element/html/form.html index f148fae0f..dba37aac8 100644 --- a/src/browser/tests/element/html/form.html +++ b/src/browser/tests/element/html/form.html @@ -138,6 +138,18 @@ } + + diff --git a/src/browser/tests/element/html/image.html b/src/browser/tests/element/html/image.html index 92cd947d6..5dafbb59a 100644 --- a/src/browser/tests/element/html/image.html +++ b/src/browser/tests/element/html/image.html @@ -114,6 +114,15 @@ } + + + + + + diff --git a/src/browser/tests/element/html/label.html b/src/browser/tests/element/html/label.html index b2ebc2cb6..5cf2b1201 100644 --- a/src/browser/tests/element/html/label.html +++ b/src/browser/tests/element/html/label.html @@ -19,6 +19,10 @@ + + + + @@ -72,3 +76,45 @@ testing.expectEqual(null, lDetached.control); } + + + + + + + + diff --git a/src/browser/tests/element/html/link.html b/src/browser/tests/element/html/link.html index bed5e6abf..7f6a1ad5b 100644 --- a/src/browser/tests/element/html/link.html +++ b/src/browser/tests/element/html/link.html @@ -25,7 +25,7 @@ // A link with rel=stylesheet and a non-empty href fires a load event when appended to the DOM const link = document.createElement('link'); link.rel = 'stylesheet'; - link.href = 'https://lightpanda.io/opensource-browser/15'; + link.href = 'link_stylesheet.css'; testing.async(async () => { const result = await new Promise(resolve => { @@ -79,8 +79,61 @@ // Append to DOM, document.head.appendChild(link); // then set href. - link.href = 'https://lightpanda.io/opensource-browser/15'; + link.href = 'link_stylesheet.css'; testing.eventually(() => testing.expectEqual(true, result)); } + + + + diff --git a/src/browser/tests/element/html/link_import_root.css b/src/browser/tests/element/html/link_import_root.css new file mode 100644 index 000000000..e79f1c651 --- /dev/null +++ b/src/browser/tests/element/html/link_import_root.css @@ -0,0 +1,2 @@ +@import "link_imported.css"; +.link-import-target { color: rgb(9, 19, 29); } diff --git a/src/browser/tests/element/html/link_imported.css b/src/browser/tests/element/html/link_imported.css new file mode 100644 index 000000000..a5ed03101 --- /dev/null +++ b/src/browser/tests/element/html/link_imported.css @@ -0,0 +1 @@ +.link-import-target { background-color: rgb(14, 144, 214); } diff --git a/src/browser/tests/element/html/link_stylesheet.css b/src/browser/tests/element/html/link_stylesheet.css new file mode 100644 index 000000000..51c0e5376 --- /dev/null +++ b/src/browser/tests/element/html/link_stylesheet.css @@ -0,0 +1,3 @@ +body { + color: rgb(12, 34, 56); +} diff --git a/src/browser/tests/element/html/script/parser_task_after_trailing_inline.html b/src/browser/tests/element/html/script/parser_task_after_trailing_inline.html new file mode 100644 index 000000000..3ada7e466 --- /dev/null +++ b/src/browser/tests/element/html/script/parser_task_after_trailing_inline.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/src/browser/tests/element/html/script/script.html b/src/browser/tests/element/html/script/script.html index 75547d405..679976705 100644 --- a/src/browser/tests/element/html/script/script.html +++ b/src/browser/tests/element/html/script/script.html @@ -7,6 +7,12 @@ let attribute_load = false; let s = document.createElement('script'); + s.crossOrigin = 'nope'; + testing.expectEqual('anonymous', s.crossOrigin); + + s.crossOrigin = 'use-Credentials'; + testing.expectEqual('use-credentials', s.crossOrigin); + document.documentElement.addEventListener('load', (e) => { testing.expectEqual(s, e.target); dom_load = true; diff --git a/src/browser/tests/event/keyboard.html b/src/browser/tests/event/keyboard.html index 79baff7ef..245f53a7e 100644 --- a/src/browser/tests/event/keyboard.html +++ b/src/browser/tests/event/keyboard.html @@ -17,6 +17,9 @@ testing.expectEqual(false, event.shiftKey); testing.expectEqual(false, event.metaKey); testing.expectEqual(false, event.altKey); + testing.expectEqual(65, event.keyCode); + testing.expectEqual(97, event.charCode); + testing.expectEqual(97, event.which); + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/net/fetch.html b/src/browser/tests/net/fetch.html index a545a4522..6989191f5 100644 --- a/src/browser/tests/net/fetch.html +++ b/src/browser/tests/net/fetch.html @@ -203,3 +203,23 @@ testing.expectEqual(true, response.body !== null); }); + + diff --git a/src/browser/tests/net/request.html b/src/browser/tests/net/request.html index 2a88e83ec..3791c675e 100644 --- a/src/browser/tests/net/request.html +++ b/src/browser/tests/net/request.html @@ -137,3 +137,14 @@ testing.expectEqual('PROPFIND', req.method); } + + diff --git a/src/browser/tests/net/websocket.html b/src/browser/tests/net/websocket.html new file mode 100644 index 000000000..846da76fa --- /dev/null +++ b/src/browser/tests/net/websocket.html @@ -0,0 +1,78 @@ + + + + + + diff --git a/src/browser/tests/page/absolute_auto_width_nav_layout.html b/src/browser/tests/page/absolute_auto_width_nav_layout.html new file mode 100644 index 000000000..78149790c --- /dev/null +++ b/src/browser/tests/page/absolute_auto_width_nav_layout.html @@ -0,0 +1,23 @@ + + + +
+ +
+
    +
  1. Search
  2. +
  3. Images
  4. +
  5. Maps
  6. +
+
+ +
+
    +
  1. Sign in
  2. +
  3. Options
  4. +
+
+ +
Body Flow
+ + diff --git a/src/browser/tests/page/absolute_position_layout.html b/src/browser/tests/page/absolute_position_layout.html new file mode 100644 index 000000000..103bad6a5 --- /dev/null +++ b/src/browser/tests/page/absolute_position_layout.html @@ -0,0 +1,12 @@ + + + + + Absolute Position Layout + + +
Left Dock
+
Right Dock
+
Body Flow
+ + diff --git a/src/browser/tests/page/absolute_zindex_layout.html b/src/browser/tests/page/absolute_zindex_layout.html new file mode 100644 index 000000000..ada79f7d9 --- /dev/null +++ b/src/browser/tests/page/absolute_zindex_layout.html @@ -0,0 +1,12 @@ + + + +
+
Flow spacer
+
+ Low Overlay + High Overlay +
Body flow
+
+ + diff --git a/src/browser/tests/page/artifact.txt b/src/browser/tests/page/artifact.txt new file mode 100644 index 000000000..6f1500806 --- /dev/null +++ b/src/browser/tests/page/artifact.txt @@ -0,0 +1 @@ +download smoke payload diff --git a/src/browser/tests/page/auth_image.html b/src/browser/tests/page/auth_image.html new file mode 100644 index 000000000..0f9fb6c36 --- /dev/null +++ b/src/browser/tests/page/auth_image.html @@ -0,0 +1,10 @@ + + + + + Auth Image + + + auth image + + diff --git a/src/browser/tests/page/auth_image_anonymous.html b/src/browser/tests/page/auth_image_anonymous.html new file mode 100644 index 000000000..b260a3b0c --- /dev/null +++ b/src/browser/tests/page/auth_image_anonymous.html @@ -0,0 +1,10 @@ + + + + + Auth Image Anonymous + + + auth image anonymous + + diff --git a/src/browser/tests/page/auth_image_inherited.html b/src/browser/tests/page/auth_image_inherited.html new file mode 100644 index 000000000..3d7a3bc34 --- /dev/null +++ b/src/browser/tests/page/auth_image_inherited.html @@ -0,0 +1,10 @@ + + + + + Inherited Auth Image + + + inherited auth image + + diff --git a/src/browser/tests/page/background_image_layout.html b/src/browser/tests/page/background_image_layout.html new file mode 100644 index 000000000..0cd98549f --- /dev/null +++ b/src/browser/tests/page/background_image_layout.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/src/browser/tests/page/background_position_layout.html b/src/browser/tests/page/background_position_layout.html new file mode 100644 index 000000000..22eb4065e --- /dev/null +++ b/src/browser/tests/page/background_position_layout.html @@ -0,0 +1,8 @@ + + + +
+
+
+ + diff --git a/src/browser/tests/page/background_size_layout.html b/src/browser/tests/page/background_size_layout.html new file mode 100644 index 000000000..cba1fbe54 --- /dev/null +++ b/src/browser/tests/page/background_size_layout.html @@ -0,0 +1,9 @@ + + + +
+
+
+
+ + diff --git a/src/browser/tests/page/background_sprite.png b/src/browser/tests/page/background_sprite.png new file mode 100644 index 000000000..65e78462d Binary files /dev/null and b/src/browser/tests/page/background_sprite.png differ diff --git a/src/browser/tests/page/body_onload_focus.html b/src/browser/tests/page/body_onload_focus.html new file mode 100644 index 000000000..291e2aef7 --- /dev/null +++ b/src/browser/tests/page/body_onload_focus.html @@ -0,0 +1,16 @@ + + + + + Body Load Pending + + + + + + diff --git a/src/browser/tests/page/body_onload_keyboard_input.html b/src/browser/tests/page/body_onload_keyboard_input.html new file mode 100644 index 000000000..29bd4133e --- /dev/null +++ b/src/browser/tests/page/body_onload_keyboard_input.html @@ -0,0 +1,28 @@ + + + + + Body Load Keyboard Pending + + + + + + diff --git a/src/browser/tests/page/border_radius_layout.html b/src/browser/tests/page/border_radius_layout.html new file mode 100644 index 000000000..dfdfeaefe --- /dev/null +++ b/src/browser/tests/page/border_radius_layout.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/src/browser/tests/page/box_shadow_layout.html b/src/browser/tests/page/box_shadow_layout.html new file mode 100644 index 000000000..f419b459d --- /dev/null +++ b/src/browser/tests/page/box_shadow_layout.html @@ -0,0 +1,28 @@ + + + + +Box Shadow - Lightpanda Browser + + + +
+ + diff --git a/src/browser/tests/page/box_sizing_layout.html b/src/browser/tests/page/box_sizing_layout.html new file mode 100644 index 000000000..70281cdaa --- /dev/null +++ b/src/browser/tests/page/box_sizing_layout.html @@ -0,0 +1,52 @@ + + + + + Box Sizing Layout - Lightpanda Browser + + + +
+
+
+
+

Below content

+ + diff --git a/src/browser/tests/page/button_render.html b/src/browser/tests/page/button_render.html new file mode 100644 index 000000000..7abcb601e --- /dev/null +++ b/src/browser/tests/page/button_render.html @@ -0,0 +1,10 @@ + + + + + Button Render + + + + + diff --git a/src/browser/tests/page/canvas_draw_image_image_render.html b/src/browser/tests/page/canvas_draw_image_image_render.html new file mode 100644 index 000000000..97d1082c0 --- /dev/null +++ b/src/browser/tests/page/canvas_draw_image_image_render.html @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_draw_image_render.html b/src/browser/tests/page/canvas_draw_image_render.html new file mode 100644 index 000000000..57d55d350 --- /dev/null +++ b/src/browser/tests/page/canvas_draw_image_render.html @@ -0,0 +1,51 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_path_render.html b/src/browser/tests/page/canvas_path_render.html new file mode 100644 index 000000000..2533d1aef --- /dev/null +++ b/src/browser/tests/page/canvas_path_render.html @@ -0,0 +1,34 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_render.html b/src/browser/tests/page/canvas_render.html new file mode 100644 index 000000000..9efe2d713 --- /dev/null +++ b/src/browser/tests/page/canvas_render.html @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_text_render.html b/src/browser/tests/page/canvas_text_render.html new file mode 100644 index 000000000..5f1735237 --- /dev/null +++ b/src/browser/tests/page/canvas_text_render.html @@ -0,0 +1,38 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_webgl_clear_render.html b/src/browser/tests/page/canvas_webgl_clear_render.html new file mode 100644 index 000000000..d5f1871d1 --- /dev/null +++ b/src/browser/tests/page/canvas_webgl_clear_render.html @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_webgl_indexed_render.html b/src/browser/tests/page/canvas_webgl_indexed_render.html new file mode 100644 index 000000000..42de0879f --- /dev/null +++ b/src/browser/tests/page/canvas_webgl_indexed_render.html @@ -0,0 +1,57 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_webgl_triangle_render.html b/src/browser/tests/page/canvas_webgl_triangle_render.html new file mode 100644 index 000000000..86298a0cd --- /dev/null +++ b/src/browser/tests/page/canvas_webgl_triangle_render.html @@ -0,0 +1,50 @@ + + + + + + + diff --git a/src/browser/tests/page/canvas_webgl_varying_render.html b/src/browser/tests/page/canvas_webgl_varying_render.html new file mode 100644 index 000000000..a4587a780 --- /dev/null +++ b/src/browser/tests/page/canvas_webgl_varying_render.html @@ -0,0 +1,57 @@ + + + + + + + diff --git a/src/browser/tests/page/custom_property_layout.html b/src/browser/tests/page/custom_property_layout.html new file mode 100644 index 000000000..06f097797 --- /dev/null +++ b/src/browser/tests/page/custom_property_layout.html @@ -0,0 +1,48 @@ + + + + + + +
+
+ Accept all +
Cookies and data
+
+
+ + diff --git a/src/browser/tests/page/fixed_position_layout.html b/src/browser/tests/page/fixed_position_layout.html new file mode 100644 index 000000000..7876dd24c --- /dev/null +++ b/src/browser/tests/page/fixed_position_layout.html @@ -0,0 +1,45 @@ + + + + + Fixed Position Layout + + + +
+ + + +
+ + diff --git a/src/browser/tests/page/flex_align_content_layout.html b/src/browser/tests/page/flex_align_content_layout.html new file mode 100644 index 000000000..4efccadb7 --- /dev/null +++ b/src/browser/tests/page/flex_align_content_layout.html @@ -0,0 +1,56 @@ + + + + Flex Align Content - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_align_self_layout.html b/src/browser/tests/page/flex_align_self_layout.html new file mode 100644 index 000000000..ba32a8767 --- /dev/null +++ b/src/browser/tests/page/flex_align_self_layout.html @@ -0,0 +1,58 @@ + + + + Flex Align Self - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_center_layout.html b/src/browser/tests/page/flex_center_layout.html new file mode 100644 index 000000000..75953baf3 --- /dev/null +++ b/src/browser/tests/page/flex_center_layout.html @@ -0,0 +1,43 @@ + + + + + + +
+

Search

+ +
+ + diff --git a/src/browser/tests/page/flex_column_grow_layout.html b/src/browser/tests/page/flex_column_grow_layout.html new file mode 100644 index 000000000..bbcec010c --- /dev/null +++ b/src/browser/tests/page/flex_column_grow_layout.html @@ -0,0 +1,34 @@ + + + + +Flex Column Grow - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_column_justify_layout.html b/src/browser/tests/page/flex_column_justify_layout.html new file mode 100644 index 000000000..58f8002af --- /dev/null +++ b/src/browser/tests/page/flex_column_justify_layout.html @@ -0,0 +1,33 @@ + + + + +Flex Column Justify - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_column_reverse_layout.html b/src/browser/tests/page/flex_column_reverse_layout.html new file mode 100644 index 000000000..01c7016c7 --- /dev/null +++ b/src/browser/tests/page/flex_column_reverse_layout.html @@ -0,0 +1,32 @@ + + + + +Flex Column Reverse - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_column_shrink_layout.html b/src/browser/tests/page/flex_column_shrink_layout.html new file mode 100644 index 000000000..462bd8f63 --- /dev/null +++ b/src/browser/tests/page/flex_column_shrink_layout.html @@ -0,0 +1,33 @@ + + + + +Flex Column Shrink - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/flex_grow_layout.html b/src/browser/tests/page/flex_grow_layout.html new file mode 100644 index 000000000..213c8b781 --- /dev/null +++ b/src/browser/tests/page/flex_grow_layout.html @@ -0,0 +1,58 @@ + + + + + + +
+ + + +
+ + diff --git a/src/browser/tests/page/flex_order_blue_target.html b/src/browser/tests/page/flex_order_blue_target.html new file mode 100644 index 000000000..729d11c8c --- /dev/null +++ b/src/browser/tests/page/flex_order_blue_target.html @@ -0,0 +1,18 @@ + + + + Flex Order Blue Target + + + + Blue target + + diff --git a/src/browser/tests/page/flex_order_green_target.html b/src/browser/tests/page/flex_order_green_target.html new file mode 100644 index 000000000..307baae69 --- /dev/null +++ b/src/browser/tests/page/flex_order_green_target.html @@ -0,0 +1,18 @@ + + + + Flex Order Green Target + + + + Green target + + diff --git a/src/browser/tests/page/flex_order_layout.html b/src/browser/tests/page/flex_order_layout.html new file mode 100644 index 000000000..242a66ff1 --- /dev/null +++ b/src/browser/tests/page/flex_order_layout.html @@ -0,0 +1,62 @@ + + + + Flex Order - Lightpanda Browser + + + +
+ RED + BLUE + GREEN +
+ + diff --git a/src/browser/tests/page/flex_order_red_target.html b/src/browser/tests/page/flex_order_red_target.html new file mode 100644 index 000000000..ec3125e0c --- /dev/null +++ b/src/browser/tests/page/flex_order_red_target.html @@ -0,0 +1,18 @@ + + + + Flex Order Red Target + + + + Red target + + diff --git a/src/browser/tests/page/flex_overflow_auto_scroll_layout.html b/src/browser/tests/page/flex_overflow_auto_scroll_layout.html new file mode 100644 index 000000000..7c01daa48 --- /dev/null +++ b/src/browser/tests/page/flex_overflow_auto_scroll_layout.html @@ -0,0 +1,15 @@ + + + +
+
+
+
+
+ + + + diff --git a/src/browser/tests/page/flex_overflow_hidden_layout.html b/src/browser/tests/page/flex_overflow_hidden_layout.html new file mode 100644 index 000000000..9d635e999 --- /dev/null +++ b/src/browser/tests/page/flex_overflow_hidden_layout.html @@ -0,0 +1,9 @@ + + + +
+
+
+ + + diff --git a/src/browser/tests/page/flex_row_stretch_layout.html b/src/browser/tests/page/flex_row_stretch_layout.html new file mode 100644 index 000000000..cc3ac9170 --- /dev/null +++ b/src/browser/tests/page/flex_row_stretch_layout.html @@ -0,0 +1,34 @@ + + + + +Flex Row Stretch - Lightpanda Browser + + + +
+ + + +
+ + diff --git a/src/browser/tests/page/flex_row_stretch_target.html b/src/browser/tests/page/flex_row_stretch_target.html new file mode 100644 index 000000000..9690d0b3b --- /dev/null +++ b/src/browser/tests/page/flex_row_stretch_target.html @@ -0,0 +1,10 @@ + + + + +Flex Row Stretch Target - Lightpanda Browser + + +

Flex row stretch target.

+ + diff --git a/src/browser/tests/page/flex_row_wrap_layout.html b/src/browser/tests/page/flex_row_wrap_layout.html new file mode 100644 index 000000000..3d889efc6 --- /dev/null +++ b/src/browser/tests/page/flex_row_wrap_layout.html @@ -0,0 +1,50 @@ + + + + + + +
+ + + +
+ + diff --git a/src/browser/tests/page/flex_shrink_layout.html b/src/browser/tests/page/flex_shrink_layout.html new file mode 100644 index 000000000..f86f424f6 --- /dev/null +++ b/src/browser/tests/page/flex_shrink_layout.html @@ -0,0 +1,56 @@ + + + + Flex Shrink - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/src/browser/tests/page/float_dock_layout.html b/src/browser/tests/page/float_dock_layout.html new file mode 100644 index 000000000..a52ecacd1 --- /dev/null +++ b/src/browser/tests/page/float_dock_layout.html @@ -0,0 +1,51 @@ + + + + + + +
+
Left Dock
+
Right Dock
+
Body Flow
+
+ + diff --git a/src/browser/tests/page/font_button_measure.html b/src/browser/tests/page/font_button_measure.html new file mode 100644 index 000000000..88f6bacaf --- /dev/null +++ b/src/browser/tests/page/font_button_measure.html @@ -0,0 +1,11 @@ + + + +
+ +
+
+ +
+ + diff --git a/src/browser/tests/page/font_private_render.html b/src/browser/tests/page/font_private_render.html new file mode 100644 index 000000000..e76f343f3 --- /dev/null +++ b/src/browser/tests/page/font_private_render.html @@ -0,0 +1,26 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/src/browser/tests/page/font_private_woff2_render.html b/src/browser/tests/page/font_private_woff2_render.html new file mode 100644 index 000000000..12ccf91ef --- /dev/null +++ b/src/browser/tests/page/font_private_woff2_render.html @@ -0,0 +1,26 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/src/browser/tests/page/font_private_woff_render.html b/src/browser/tests/page/font_private_woff_render.html new file mode 100644 index 000000000..4b48e7078 --- /dev/null +++ b/src/browser/tests/page/font_private_woff_render.html @@ -0,0 +1,26 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/src/browser/tests/page/font_render.html b/src/browser/tests/page/font_render.html new file mode 100644 index 000000000..14ef524e2 --- /dev/null +++ b/src/browser/tests/page/font_render.html @@ -0,0 +1,11 @@ + + + +
+ MMMMMMMMMMMM +
+
+ NNNNNNNNNNNN +
+ + diff --git a/src/browser/tests/page/google_tab_text_inheritance_layout.html b/src/browser/tests/page/google_tab_text_inheritance_layout.html new file mode 100644 index 000000000..656a5b8ba --- /dev/null +++ b/src/browser/tests/page/google_tab_text_inheritance_layout.html @@ -0,0 +1,68 @@ + + + + + + + + + + diff --git a/src/browser/tests/page/image_layout_fidelity.html b/src/browser/tests/page/image_layout_fidelity.html new file mode 100644 index 000000000..3ab05609b --- /dev/null +++ b/src/browser/tests/page/image_layout_fidelity.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/browser/tests/page/inline_block_tabs_layout.html b/src/browser/tests/page/inline_block_tabs_layout.html new file mode 100644 index 000000000..74478f50b --- /dev/null +++ b/src/browser/tests/page/inline_block_tabs_layout.html @@ -0,0 +1,45 @@ + + + + + + +
+
    +
  1. Search
  2. +
  3. Images
  4. +
  5. Maps
  6. +
+
+ + diff --git a/src/browser/tests/page/inline_block_wrapper_layout.html b/src/browser/tests/page/inline_block_wrapper_layout.html new file mode 100644 index 000000000..8a9221299 --- /dev/null +++ b/src/browser/tests/page/inline_block_wrapper_layout.html @@ -0,0 +1,29 @@ + + + + + + + Alpha + Beta + + diff --git a/src/browser/tests/page/input_submit_sizing_layout.html b/src/browser/tests/page/input_submit_sizing_layout.html new file mode 100644 index 000000000..72c56907f --- /dev/null +++ b/src/browser/tests/page/input_submit_sizing_layout.html @@ -0,0 +1,9 @@ + + + +
+ + +
+ + diff --git a/src/browser/tests/page/input_submit_wrapper_hidden_layout.html b/src/browser/tests/page/input_submit_wrapper_hidden_layout.html new file mode 100644 index 000000000..44dd188e5 --- /dev/null +++ b/src/browser/tests/page/input_submit_wrapper_hidden_layout.html @@ -0,0 +1,16 @@ + + + +
+ + + + + + + +
+ + diff --git a/src/browser/tests/page/internal_browser_link.html b/src/browser/tests/page/internal_browser_link.html new file mode 100644 index 000000000..ffdf64dfa --- /dev/null +++ b/src/browser/tests/page/internal_browser_link.html @@ -0,0 +1,10 @@ + + + + + Internal Browser Link + + + History + + diff --git a/src/browser/tests/page/intrinsic_image_layout.html b/src/browser/tests/page/intrinsic_image_layout.html new file mode 100644 index 000000000..1575d9fe9 --- /dev/null +++ b/src/browser/tests/page/intrinsic_image_layout.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/browser/tests/page/keyboard_code_focus_input.html b/src/browser/tests/page/keyboard_code_focus_input.html new file mode 100644 index 000000000..3cadd46c4 --- /dev/null +++ b/src/browser/tests/page/keyboard_code_focus_input.html @@ -0,0 +1,29 @@ + + + + + Keyboard Code Pending + + + + + + diff --git a/src/browser/tests/page/layout_tall_blue.png b/src/browser/tests/page/layout_tall_blue.png new file mode 100644 index 000000000..fbf7ad9db Binary files /dev/null and b/src/browser/tests/page/layout_tall_blue.png differ diff --git a/src/browser/tests/page/legacy_table_layout.html b/src/browser/tests/page/legacy_table_layout.html new file mode 100644 index 000000000..ba1a8917d --- /dev/null +++ b/src/browser/tests/page/legacy_table_layout.html @@ -0,0 +1,90 @@ + + + + + + +
+ +
+ + + + + + +
  +
+
+ + +
+
Advanced
+
+
+
+ + diff --git a/src/browser/tests/page/logical_properties_layout.html b/src/browser/tests/page/logical_properties_layout.html new file mode 100644 index 000000000..b06d9dbbb --- /dev/null +++ b/src/browser/tests/page/logical_properties_layout.html @@ -0,0 +1,51 @@ + + + +
+
+
+
+ +
+ + diff --git a/src/browser/tests/page/min_max_height_layout.html b/src/browser/tests/page/min_max_height_layout.html new file mode 100644 index 000000000..208382dda --- /dev/null +++ b/src/browser/tests/page/min_max_height_layout.html @@ -0,0 +1,10 @@ + + + +
+
+
+
+ + + diff --git a/src/browser/tests/page/mixed_inline_break_flow.html b/src/browser/tests/page/mixed_inline_break_flow.html new file mode 100644 index 000000000..ef25175e7 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_break_flow.html @@ -0,0 +1,21 @@ + + + + + Mixed Inline Break Flow + + +
+

+ Prefix text + RED +
+ After break + GREEN + and + LINK now +

+

Below paragraph stays below break flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_button_flow.html b/src/browser/tests/page/mixed_inline_button_flow.html new file mode 100644 index 000000000..db3df178e --- /dev/null +++ b/src/browser/tests/page/mixed_inline_button_flow.html @@ -0,0 +1,19 @@ + + + + + Mixed Inline Button Flow + + +
+

+ Lead text before the inline control + + trailing text after it +

+

Below paragraph stays below the wrapped inline control.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_button_link_flow.html b/src/browser/tests/page/mixed_inline_checkbox_button_link_flow.html new file mode 100644 index 000000000..06533069b --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_button_link_flow.html @@ -0,0 +1,28 @@ + + + + + Mixed Inline Checkbox Button Link Flow + + +
+

+ Lead text before the checkbox + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox, button, and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_link_flow.html b/src/browser/tests/page/mixed_inline_checkbox_link_flow.html new file mode 100644 index 000000000..45970e97d --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_link_flow.html @@ -0,0 +1,21 @@ + + + + + Mixed Inline Checkbox Link Flow + + +
+

+ Lead text before the checkbox + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_button_link_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_button_link_flow.html new file mode 100644 index 000000000..1bb696fd5 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_button_link_flow.html @@ -0,0 +1,35 @@ + + + + + Mixed Inline Checkbox Pair Button Link Flow + + +
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox pair, button, and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_input_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_input_submit_flow.html new file mode 100644 index 000000000..8e1ca6cd4 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_input_submit_flow.html @@ -0,0 +1,41 @@ + + + + + Mixed Inline Checkbox Pair Input Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, input, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_input_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_input_submit_flow.html new file mode 100644 index 000000000..3a2816d06 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_input_submit_flow.html @@ -0,0 +1,55 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Input Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, input, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_eight_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_eight_link_submit_flow.html new file mode 100644 index 000000000..581daa4ed --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_eight_link_submit_flow.html @@ -0,0 +1,46 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Eight Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before the seventh link + Next Seven + bridge words before the eighth link + Next Eight + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, eight links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_five_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_five_link_submit_flow.html new file mode 100644 index 000000000..713a4c420 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_five_link_submit_flow.html @@ -0,0 +1,40 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Five Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, five links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_four_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_four_link_submit_flow.html new file mode 100644 index 000000000..a575d8cb3 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_four_link_submit_flow.html @@ -0,0 +1,38 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Four Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, four links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_seven_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_seven_link_submit_flow.html new file mode 100644 index 000000000..aae3cf94a --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_seven_link_submit_flow.html @@ -0,0 +1,44 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Seven Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before the seventh link + Next Seven + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, seven links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_six_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_six_link_submit_flow.html new file mode 100644 index 000000000..d49abfebb --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_six_link_submit_flow.html @@ -0,0 +1,42 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Six Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, six links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_flow.html new file mode 100644 index 000000000..8c0c54768 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_flow.html @@ -0,0 +1,30 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_link_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_link_flow.html new file mode 100644 index 000000000..05966dbc2 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_submit_link_flow.html @@ -0,0 +1,32 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Submit Link Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the link + Next + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, link, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_three_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_three_link_submit_flow.html new file mode 100644 index 000000000..b9c4c354a --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_three_link_submit_flow.html @@ -0,0 +1,36 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Three Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, three links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_two_link_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_two_link_submit_flow.html new file mode 100644 index 000000000..ecbe42cfa --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_radio_pair_two_input_two_link_submit_flow.html @@ -0,0 +1,34 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Two Link Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, two links, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_pair_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_pair_submit_flow.html new file mode 100644 index 000000000..27a2f9849 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_pair_submit_flow.html @@ -0,0 +1,34 @@ + + + + + Mixed Inline Checkbox Pair Submit Flow + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_radio_button_link_flow.html b/src/browser/tests/page/mixed_inline_checkbox_radio_button_link_flow.html new file mode 100644 index 000000000..b46a66c9d --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_radio_button_link_flow.html @@ -0,0 +1,36 @@ + + + + + Mixed Inline Checkbox Radio Button Link Flow + + +
+

+ Lead text before the checkbox + + bridge words before the radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox, radio, button, and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_checkbox_radio_input_submit_flow.html b/src/browser/tests/page/mixed_inline_checkbox_radio_input_submit_flow.html new file mode 100644 index 000000000..e01cde4b0 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_checkbox_radio_input_submit_flow.html @@ -0,0 +1,41 @@ + + + + + Mixed Inline Checkbox Radio Input Submit Flow + + +
+
+

+ Lead text before the checkbox + + bridge words before the radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox, radio, input, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_control_link_flow.html b/src/browser/tests/page/mixed_inline_control_link_flow.html new file mode 100644 index 000000000..c8d46f860 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_control_link_flow.html @@ -0,0 +1,23 @@ + + + + + Mixed Inline Control Link Flow + + +
+

+ Lead text before button + + middle words that wrap before the link + + LINK + now + +

+

Below paragraph stays below the mixed control and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_dense_focus_flow.html b/src/browser/tests/page/mixed_inline_dense_focus_flow.html new file mode 100644 index 000000000..4bb23baec --- /dev/null +++ b/src/browser/tests/page/mixed_inline_dense_focus_flow.html @@ -0,0 +1,25 @@ + + + + + Mixed Inline Dense Focus Flow + + +
+

+ Lead text before + + bridge words before the input + + trailing words before the + + LINK + now + +

+

Below paragraph stays below the dense mixed inline flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_flow.html b/src/browser/tests/page/mixed_inline_flow.html new file mode 100644 index 000000000..7856ab072 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_flow.html @@ -0,0 +1,19 @@ + + + + + Mixed Inline Flow + + +
+

+ Prefix text + RED + middle + GREEN + go LINK now + suffix text +

+
+ + diff --git a/src/browser/tests/page/mixed_inline_input_break_flow.html b/src/browser/tests/page/mixed_inline_input_break_flow.html new file mode 100644 index 000000000..6fff377e5 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_input_break_flow.html @@ -0,0 +1,19 @@ + + + + + Mixed Inline Input Break Flow + + +
+

+ Prefix text before the explicit break +
+ After break + + tail text after input +

+

Below paragraph stays below the break and inline input.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_radio_button_link_flow.html b/src/browser/tests/page/mixed_inline_radio_button_link_flow.html new file mode 100644 index 000000000..9d4646e37 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_radio_button_link_flow.html @@ -0,0 +1,30 @@ + + + + + Mixed Inline Radio Button Link Flow + + +
+

+ Lead text before the radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio, button, and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_radio_link_flow.html b/src/browser/tests/page/mixed_inline_radio_link_flow.html new file mode 100644 index 000000000..7e1c68e98 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_radio_link_flow.html @@ -0,0 +1,21 @@ + + + + + Mixed Inline Radio Link Flow + + +
+

+ Lead text before the radio + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_radio_pair_button_link_flow.html b/src/browser/tests/page/mixed_inline_radio_pair_button_link_flow.html new file mode 100644 index 000000000..14e130d54 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_radio_pair_button_link_flow.html @@ -0,0 +1,37 @@ + + + + + Mixed Inline Radio Pair Button Link Flow + + +
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio pair, button, and link flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_radio_pair_input_submit_flow.html b/src/browser/tests/page/mixed_inline_radio_pair_input_submit_flow.html new file mode 100644 index 000000000..39cfc7e25 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_radio_pair_input_submit_flow.html @@ -0,0 +1,41 @@ + + + + + Mixed Inline Radio Pair Input Submit Flow + + +
+
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the radio pair, input, and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_radio_pair_submit_flow.html b/src/browser/tests/page/mixed_inline_radio_pair_submit_flow.html new file mode 100644 index 000000000..c6df453bc --- /dev/null +++ b/src/browser/tests/page/mixed_inline_radio_pair_submit_flow.html @@ -0,0 +1,34 @@ + + + + + Mixed Inline Radio Pair Submit Flow + + +
+
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before submit + +

+
+

Below paragraph stays below the radio pair and submit flow.

+
+ + diff --git a/src/browser/tests/page/mixed_inline_wrap_flow.html b/src/browser/tests/page/mixed_inline_wrap_flow.html new file mode 100644 index 000000000..26be69611 --- /dev/null +++ b/src/browser/tests/page/mixed_inline_wrap_flow.html @@ -0,0 +1,20 @@ + + + + + Mixed Inline Wrap Flow + + +
+

+ Prefix text + RED + middle words that should wrap with + GREEN + and + go LINK now suffix text +

+

Below paragraph stays below wrapped inline content.

+
+ + diff --git a/src/browser/tests/page/mouse_down_focus_input.html b/src/browser/tests/page/mouse_down_focus_input.html new file mode 100644 index 000000000..bdda3f618 --- /dev/null +++ b/src/browser/tests/page/mouse_down_focus_input.html @@ -0,0 +1,38 @@ + + +Mouse Down Focus + +
+ +
+ diff --git a/src/browser/tests/page/nowrap_text_layout.html b/src/browser/tests/page/nowrap_text_layout.html new file mode 100644 index 000000000..8e95d8f71 --- /dev/null +++ b/src/browser/tests/page/nowrap_text_layout.html @@ -0,0 +1,24 @@ + + + + + + +
Sign in
+ + diff --git a/src/browser/tests/page/opacity_rendering_layout.html b/src/browser/tests/page/opacity_rendering_layout.html new file mode 100644 index 000000000..082b8d6c1 --- /dev/null +++ b/src/browser/tests/page/opacity_rendering_layout.html @@ -0,0 +1,33 @@ + + + + + + + +
+ Outer +
+ Inner +
+
+ + diff --git a/src/browser/tests/page/overflow_auto_link_scroll_layout.html b/src/browser/tests/page/overflow_auto_link_scroll_layout.html new file mode 100644 index 000000000..f3eb42424 --- /dev/null +++ b/src/browser/tests/page/overflow_auto_link_scroll_layout.html @@ -0,0 +1,13 @@ + + + +
+
+ scroll link +
+ + + diff --git a/src/browser/tests/page/overflow_auto_scroll_layout.html b/src/browser/tests/page/overflow_auto_scroll_layout.html new file mode 100644 index 000000000..ff3886558 --- /dev/null +++ b/src/browser/tests/page/overflow_auto_scroll_layout.html @@ -0,0 +1,15 @@ + + + +
+
+
+
+
+ + + + diff --git a/src/browser/tests/page/overflow_hidden_layout.html b/src/browser/tests/page/overflow_hidden_layout.html new file mode 100644 index 000000000..b56a51de4 --- /dev/null +++ b/src/browser/tests/page/overflow_hidden_layout.html @@ -0,0 +1,9 @@ + + + +
+
+
+ + + diff --git a/src/browser/tests/page/overflow_hidden_link_layout.html b/src/browser/tests/page/overflow_hidden_link_layout.html new file mode 100644 index 000000000..d8684ed61 --- /dev/null +++ b/src/browser/tests/page/overflow_hidden_link_layout.html @@ -0,0 +1,12 @@ + + + + Overflow Hidden Link Layout - Lightpanda Browser + + + + + + diff --git a/src/browser/tests/page/pill_split_word_controls_layout.html b/src/browser/tests/page/pill_split_word_controls_layout.html new file mode 100644 index 000000000..426dfb26a --- /dev/null +++ b/src/browser/tests/page/pill_split_word_controls_layout.html @@ -0,0 +1,54 @@ + + + + + + +
+ + +
+ + diff --git a/src/browser/tests/page/popup_target.html b/src/browser/tests/page/popup_target.html new file mode 100644 index 000000000..b9f42ceea --- /dev/null +++ b/src/browser/tests/page/popup_target.html @@ -0,0 +1,12 @@ + +Open named target + +
+ + +
+ +
+ + +
diff --git a/src/browser/tests/page/rendered_download_activation.html b/src/browser/tests/page/rendered_download_activation.html new file mode 100644 index 000000000..6740eafdf --- /dev/null +++ b/src/browser/tests/page/rendered_download_activation.html @@ -0,0 +1,17 @@ + + + + + Download Smoke + + +
+

Download Smoke

+

+ + Download Example File + +

+
+ + diff --git a/src/browser/tests/page/rendered_link_activation.html b/src/browser/tests/page/rendered_link_activation.html new file mode 100644 index 000000000..8d85b57ae --- /dev/null +++ b/src/browser/tests/page/rendered_link_activation.html @@ -0,0 +1,33 @@ + + + + + Rendered Link Activation Start + + +
+ PREVENT DEFAULT + MUTATE HREF +
+ + + diff --git a/src/browser/tests/page/responsive_image_layout.html b/src/browser/tests/page/responsive_image_layout.html new file mode 100644 index 000000000..067088c5d --- /dev/null +++ b/src/browser/tests/page/responsive_image_layout.html @@ -0,0 +1,8 @@ + + + +
+ +
+ + diff --git a/src/browser/tests/page/selector_compat.html b/src/browser/tests/page/selector_compat.html new file mode 100644 index 000000000..9c06ab387 --- /dev/null +++ b/src/browser/tests/page/selector_compat.html @@ -0,0 +1,35 @@ + + + + + + + +
+ Open +
Visible
+
+ + diff --git a/src/browser/tests/page/selector_error_containment.html b/src/browser/tests/page/selector_error_containment.html new file mode 100644 index 000000000..2ec166e9f --- /dev/null +++ b/src/browser/tests/page/selector_error_containment.html @@ -0,0 +1,17 @@ + + + + Selector Error Pending + + + + + diff --git a/src/browser/tests/page/selector_error_microtask_containment.html b/src/browser/tests/page/selector_error_microtask_containment.html new file mode 100644 index 000000000..6b458b415 --- /dev/null +++ b/src/browser/tests/page/selector_error_microtask_containment.html @@ -0,0 +1,16 @@ + + + + Selector Microtask Pending + + + + + diff --git a/src/browser/tests/page/selector_forgiving_stylesheet.html b/src/browser/tests/page/selector_forgiving_stylesheet.html new file mode 100644 index 000000000..5b0a0760c --- /dev/null +++ b/src/browser/tests/page/selector_forgiving_stylesheet.html @@ -0,0 +1,19 @@ + + + + + + +
VISIBLE HERO
+
HIDDEN DUPLICATE
+ + diff --git a/src/browser/tests/page/selector_has_relative.html b/src/browser/tests/page/selector_has_relative.html new file mode 100644 index 000000000..0bce7508f --- /dev/null +++ b/src/browser/tests/page/selector_has_relative.html @@ -0,0 +1,23 @@ + + + + + Selector Has Relative + + +
+
direct
+
+ +
+
attr
+
+ +
    +
  • zero
  • +
  • one
  • +
  • two
  • +
  • three
  • +
+ + diff --git a/src/browser/tests/page/text_align_center_layout.html b/src/browser/tests/page/text_align_center_layout.html new file mode 100644 index 000000000..11a6e5f5c --- /dev/null +++ b/src/browser/tests/page/text_align_center_layout.html @@ -0,0 +1,12 @@ + + + + + Text Align Center Layout + + +
+ +
+ + diff --git a/src/browser/tests/page/text_style_rendering_layout.html b/src/browser/tests/page/text_style_rendering_layout.html new file mode 100644 index 000000000..53da25ee1 --- /dev/null +++ b/src/browser/tests/page/text_style_rendering_layout.html @@ -0,0 +1,39 @@ + + + + + + +
Mono
+
Mono
+
Alpha Beta
+
Alpha Beta
+
google
+ + diff --git a/src/browser/tests/page/trailing_inline_after_scheduled_script.html b/src/browser/tests/page/trailing_inline_after_scheduled_script.html new file mode 100644 index 000000000..8b42316d6 --- /dev/null +++ b/src/browser/tests/page/trailing_inline_after_scheduled_script.html @@ -0,0 +1,13 @@ + + + +ok diff --git a/src/browser/tests/page/trailing_inline_after_timeout_script.html b/src/browser/tests/page/trailing_inline_after_timeout_script.html new file mode 100644 index 000000000..2abed5a6c --- /dev/null +++ b/src/browser/tests/page/trailing_inline_after_timeout_script.html @@ -0,0 +1,13 @@ + + + +ok diff --git a/src/browser/tests/page/transform_default_layout.html b/src/browser/tests/page/transform_default_layout.html new file mode 100644 index 000000000..b1aa36bdd --- /dev/null +++ b/src/browser/tests/page/transform_default_layout.html @@ -0,0 +1,26 @@ + + + + +Transform Default Layout - Lightpanda Browser + + + +
Plain Box
+ + diff --git a/src/browser/tests/page/transform_translate_button_auto_layout.html b/src/browser/tests/page/transform_translate_button_auto_layout.html new file mode 100644 index 000000000..75c9f1c94 --- /dev/null +++ b/src/browser/tests/page/transform_translate_button_auto_layout.html @@ -0,0 +1,95 @@ + + + + +Transform Translate Button Layout - Lightpanda Browser + + + + +
+
+
Centered Translate Shell
+
+
+ +
+ +
+ + diff --git a/src/browser/tests/page/transform_translate_layout.html b/src/browser/tests/page/transform_translate_layout.html new file mode 100644 index 000000000..fa44483ee --- /dev/null +++ b/src/browser/tests/page/transform_translate_layout.html @@ -0,0 +1,99 @@ + + + + +Transform Translate Layout - Lightpanda Browser + + + +
+
+
Centered Translate Shell
+
+
+ +
+ +
+ + diff --git a/src/browser/tests/page/transform_translate_link_layout.html b/src/browser/tests/page/transform_translate_link_layout.html new file mode 100644 index 000000000..eb29d488b --- /dev/null +++ b/src/browser/tests/page/transform_translate_link_layout.html @@ -0,0 +1,63 @@ + + + + +Transform Translate Link Layout - Lightpanda Browser + + + +
+
+
Centered Translate Link
+ Open Target +
+
+ + diff --git a/src/browser/tests/page/transform_translate_target.html b/src/browser/tests/page/transform_translate_target.html new file mode 100644 index 000000000..4ad9e3e75 --- /dev/null +++ b/src/browser/tests/page/transform_translate_target.html @@ -0,0 +1,30 @@ + + + + +Transform Translate Target - Lightpanda Browser + + + +
Transform Translate Target
+ + diff --git a/src/browser/tests/page/upload_form.html b/src/browser/tests/page/upload_form.html new file mode 100644 index 000000000..d1cc03b77 --- /dev/null +++ b/src/browser/tests/page/upload_form.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/src/browser/tests/storage.html b/src/browser/tests/storage.html index cb41bb3d3..85f7fb54a 100644 --- a/src/browser/tests/storage.html +++ b/src/browser/tests/storage.html @@ -1,6 +1,7 @@ + + + + diff --git a/src/browser/tests/window/window.html b/src/browser/tests/window/window.html index 8a0240891..98148522e 100644 --- a/src/browser/tests/window/window.html +++ b/src/browser/tests/window/window.html @@ -6,11 +6,36 @@ testing.expectEqual(window, self); testing.expectEqual(window, window.self); testing.expectEqual(null, window.opener); + testing.expectEqual('function', typeof window.open); + + const open_ref = window.open; + testing.expectEqual('function', typeof open_ref); + testing.expectEqual('function', eval('typeof window.open')); testing.expectEqual(1080, innerHeight); testing.expectEqual(1920, innerWidth); + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/drawimage.html b/tmp-browser-smoke/canvas-smoke/drawimage.html new file mode 100644 index 000000000..57d55d350 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/drawimage.html @@ -0,0 +1,51 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/index.html b/tmp-browser-smoke/canvas-smoke/index.html new file mode 100644 index 000000000..22daac8bd --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/index.html @@ -0,0 +1,24 @@ + + + + + Canvas Smoke Boot + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/measuretext.html b/tmp-browser-smoke/canvas-smoke/measuretext.html new file mode 100644 index 000000000..a2e16304e --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/measuretext.html @@ -0,0 +1,26 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/path.html b/tmp-browser-smoke/canvas-smoke/path.html new file mode 100644 index 000000000..b22cf2e2a --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/path.html @@ -0,0 +1,30 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/text.html b/tmp-browser-smoke/canvas-smoke/text.html new file mode 100644 index 000000000..17d413818 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/text.html @@ -0,0 +1,39 @@ + + + + + Canvas Text Boot + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/webgl-clear.html b/tmp-browser-smoke/canvas-smoke/webgl-clear.html new file mode 100644 index 000000000..54ac903b9 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/webgl-clear.html @@ -0,0 +1,13 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/webgl-dimensions.html b/tmp-browser-smoke/canvas-smoke/webgl-dimensions.html new file mode 100644 index 000000000..080036579 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/webgl-dimensions.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/webgl-indexed.html b/tmp-browser-smoke/canvas-smoke/webgl-indexed.html new file mode 100644 index 000000000..d853f8917 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/webgl-indexed.html @@ -0,0 +1,57 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/webgl-triangle.html b/tmp-browser-smoke/canvas-smoke/webgl-triangle.html new file mode 100644 index 000000000..0dd0bcc90 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/webgl-triangle.html @@ -0,0 +1,50 @@ + + + + + + + diff --git a/tmp-browser-smoke/canvas-smoke/webgl-varying.html b/tmp-browser-smoke/canvas-smoke/webgl-varying.html new file mode 100644 index 000000000..47cdba232 --- /dev/null +++ b/tmp-browser-smoke/canvas-smoke/webgl-varying.html @@ -0,0 +1,57 @@ + + + + + + + diff --git a/tmp-browser-smoke/common/Win32Input.ps1 b/tmp-browser-smoke/common/Win32Input.ps1 new file mode 100644 index 000000000..e0149c850 --- /dev/null +++ b/tmp-browser-smoke/common/Win32Input.ps1 @@ -0,0 +1,1221 @@ +if (-not ("SmokeProbeUser32" -as [type])) { + Add-Type @" +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +public static class SmokeProbeUser32 { + [StructLayout(LayoutKind.Sequential)] + public struct POINT { + public int X; + public int Y; + } + + [StructLayout(LayoutKind.Sequential)] + private struct INPUT { + public uint type; + public InputUnion U; + } + + [StructLayout(LayoutKind.Explicit)] + private struct InputUnion { + [FieldOffset(0)] + public MOUSEINPUT mi; + [FieldOffset(0)] + public KEYBDINPUT ki; + } + + [StructLayout(LayoutKind.Sequential)] + private struct MOUSEINPUT { + public int dx; + public int dy; + public uint mouseData; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KEYBDINPUT { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + private const uint INPUT_MOUSE = 0; + private const uint INPUT_KEYBOARD = 1; + private const uint MOUSEEVENTF_LEFTDOWN = 0x0002; + private const uint MOUSEEVENTF_LEFTUP = 0x0004; + private const uint MOUSEEVENTF_WHEEL = 0x0800; + private const uint KEYEVENTF_KEYUP = 0x0002; + private const uint KEYEVENTF_UNICODE = 0x0004; + private const ushort VK_CONTROL = 0x11; + private const ushort VK_SHIFT = 0x10; + private const ushort VK_SPACE = 0x20; + private const ushort VK_RETURN = 0x0D; + private const ushort VK_TAB = 0x09; + private const ushort VK_PRIOR = 0x21; + private const ushort VK_NEXT = 0x22; + private const ushort VK_END = 0x23; + private const ushort VK_HOME = 0x24; + private const ushort VK_LEFT = 0x25; + private const ushort VK_UP = 0x26; + private const ushort VK_RIGHT = 0x27; + private const ushort VK_DOWN = 0x28; + private const ushort VK_DELETE = 0x2E; + private const ushort VK_A = 0x41; + private const ushort VK_B = 0x42; + private const ushort VK_D = 0x44; + private const ushort VK_F = 0x46; + private const ushort VK_H = 0x48; + private const ushort VK_J = 0x4A; + private const ushort VK_L = 0x4C; + private const ushort VK_S = 0x53; + private const ushort VK_T = 0x54; + private const ushort VK_W = 0x57; + private const ushort VK_MENU = 0x12; + private const ushort VK_ESCAPE = 0x1B; + private const ushort VK_F3 = 0x72; + private const ushort VK_F5 = 0x74; + private const ushort VK_OEM_COMMA = 0xBC; + private const ushort VK_OEM_PLUS = 0xBB; + private const ushort VK_P = 0x50; + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetActiveWindow(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetCursorPos(int X, int Y); + + [DllImport("user32.dll", SetLastError = true)] + private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int GetWindowTextW(IntPtr hWnd, StringBuilder text, int count); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + + private static void EnsureSent(uint sent, int expected, string label) { + if (sent == (uint)expected) { + return; + } + throw new Win32Exception(Marshal.GetLastWin32Error(), label + " sent " + sent + " of " + expected); + } + + public static void SendLeftDown() { + var inputs = new INPUT[1]; + inputs[0].type = INPUT_MOUSE; + inputs[0].U.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendLeftDown"); + } + + public static void SendLeftUp() { + var inputs = new INPUT[1]; + inputs[0].type = INPUT_MOUSE; + inputs[0].U.mi.dwFlags = MOUSEEVENTF_LEFTUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendLeftUp"); + } + + public static void SendLeftClick() { + SendLeftDown(); + SendLeftUp(); + } + + public static void SendMouseWheel(int delta) { + var inputs = new INPUT[1]; + inputs[0].type = INPUT_MOUSE; + inputs[0].U.mi.dwFlags = MOUSEEVENTF_WHEEL; + inputs[0].U.mi.mouseData = (uint)delta; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendMouseWheel"); + } + + public static void SendVirtualKey(ushort vk) { + var inputs = new INPUT[2]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = vk; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = vk; + inputs[1].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendVirtualKey"); + } + + public static void SendCtrlA() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_A; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_A; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlA"); + } + + public static void SendCtrlF() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_F; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_F; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlF"); + } + + public static void SendCtrlD() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_D; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_D; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlD"); + } + + public static void SendCtrlH() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_H; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_H; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlH"); + } + + public static void SendCtrlJ() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_J; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_J; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlJ"); + } + + public static void SendCtrlL() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_L; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_L; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlL"); + } + + public static void SendCtrlComma() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_OEM_COMMA; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_OEM_COMMA; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlComma"); + } + + public static void SendCtrlT() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_T; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_T; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlT"); + } + + public static void SendCtrlW() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_W; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_W; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlW"); + } + + public static void SendCtrlTab() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_TAB; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_TAB; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlTab"); + } + + public static void SendCtrlShiftTab() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_TAB; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_TAB; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftTab"); + } + + public static void SendCtrlDigit(ushort vk) { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = vk; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = vk; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlDigit"); + } + + public static void SendCtrlPlus() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_OEM_PLUS; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_OEM_PLUS; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_CONTROL; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlPlus"); + } + + public static void SendCtrlShiftP() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_P; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_P; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftP"); + } + + public static void SendCtrlShiftA() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_A; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_A; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftA"); + } + + public static void SendCtrlShiftB() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_B; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_B; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftB"); + } + + public static void SendCtrlShiftT() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_T; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_T; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftT"); + } + + public static void SendCtrlShiftD() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_SHIFT; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_D; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_D; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_SHIFT; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlShiftD"); + } + + public static void SendCtrlAltH() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_MENU; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_H; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_H; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_MENU; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlAltH"); + } + + public static void SendCtrlAltB() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_MENU; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_B; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_B; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_MENU; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlAltB"); + } + + public static void SendCtrlAltJ() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_MENU; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_J; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_J; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_MENU; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlAltJ"); + } + + public static void SendCtrlAltS() { + var inputs = new INPUT[6]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_MENU; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_S; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_S; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[4].type = INPUT_KEYBOARD; + inputs[4].U.ki.wVk = VK_MENU; + inputs[4].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[5].type = INPUT_KEYBOARD; + inputs[5].U.ki.wVk = VK_CONTROL; + inputs[5].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlAltS"); + } + + public static void SendAltHome() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_MENU; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_HOME; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_HOME; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_MENU; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendAltHome"); + } + + public static void SendCtrlWheel(int delta) { + var inputs = new INPUT[3]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_CONTROL; + inputs[1].type = INPUT_MOUSE; + inputs[1].U.mi.dwFlags = MOUSEEVENTF_WHEEL; + inputs[1].U.mi.mouseData = (uint)delta; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_CONTROL; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendCtrlWheel"); + } + + public static void SendEscape() { + SendVirtualKey(VK_ESCAPE); + } + + public static void SendF3() { + SendVirtualKey(VK_F3); + } + + public static void SendF5() { + SendVirtualKey(VK_F5); + } + + public static void SendShiftF3() { + var inputs = new INPUT[4]; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].U.ki.wVk = VK_SHIFT; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].U.ki.wVk = VK_F3; + inputs[2].type = INPUT_KEYBOARD; + inputs[2].U.ki.wVk = VK_F3; + inputs[2].U.ki.dwFlags = KEYEVENTF_KEYUP; + inputs[3].type = INPUT_KEYBOARD; + inputs[3].U.ki.wVk = VK_SHIFT; + inputs[3].U.ki.dwFlags = KEYEVENTF_KEYUP; + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendShiftF3"); + } + + public static void SendEnter() { + SendVirtualKey(VK_RETURN); + } + + public static void SendSpace() { + SendVirtualKey(VK_SPACE); + } + + public static void SendTab() { + SendVirtualKey(VK_TAB); + } + + public static void SendUp() { + SendVirtualKey(VK_UP); + } + + public static void SendDown() { + SendVirtualKey(VK_DOWN); + } + + public static void SendHome() { + SendVirtualKey(VK_HOME); + } + + public static void SendEnd() { + SendVirtualKey(VK_END); + } + + public static void SendPageUp() { + SendVirtualKey(VK_PRIOR); + } + + public static void SendPageDown() { + SendVirtualKey(VK_NEXT); + } + + public static void SendDelete() { + SendVirtualKey(VK_DELETE); + } + + public static void SendUnicodeString(string text) { + if (String.IsNullOrEmpty(text)) { + return; + } + + var inputs = new INPUT[text.Length * 2]; + var i = 0; + foreach (var ch in text) { + inputs[i].type = INPUT_KEYBOARD; + inputs[i].U.ki.wScan = ch; + inputs[i].U.ki.dwFlags = KEYEVENTF_UNICODE; + i += 1; + + inputs[i].type = INPUT_KEYBOARD; + inputs[i].U.ki.wScan = ch; + inputs[i].U.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; + i += 1; + } + + EnsureSent(SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))), inputs.Length, "SendUnicodeString"); + } +} +"@ +} + +function Use-BareMetalInput { + return -not [string]::IsNullOrWhiteSpace($env:LIGHTPANDA_BARE_METAL_INPUT) +} + +function Use-HeadedMailboxInput { + return -not [string]::IsNullOrWhiteSpace($env:LIGHTPANDA_WIN32_INPUT) +} + +function Get-HeadedMailboxInputPath { + if (Use-HeadedMailboxInput) { + return $env:LIGHTPANDA_WIN32_INPUT + } + return $null +} + +function Write-HeadedMailboxLine([string]$Line) { + $path = Get-HeadedMailboxInputPath + if (-not $path) { + return $false + } + + $parent = Split-Path -Parent $path + if ($parent) { + New-Item -ItemType Directory -Force -Path $parent | Out-Null + } + + $encoding = [System.Text.UTF8Encoding]::new($false) + [System.IO.File]::AppendAllText($path, $Line + [Environment]::NewLine, $encoding) + return $true +} + +function Send-HeadedKeyStroke([int]$Code, [int]$Modifiers = 0) { + [void](Write-HeadedMailboxLine ("key|{0}|1|{1}" -f $Code, $Modifiers)) + [void](Write-HeadedMailboxLine ("key|{0}|0|{1}" -f $Code, $Modifiers)) +} + +function Send-HeadedPointerClick([int]$X, [int]$Y, [string]$Button = 'left', [int]$Modifiers = 0) { + [void](Write-HeadedMailboxLine ("click|{0}|{1}|{2}|{3}" -f $X, $Y, $Button, $Modifiers)) +} + +function Send-HeadedWheel([int]$X, [int]$Y, [int]$Delta, [int]$Modifiers = 0) { + [void](Write-HeadedMailboxLine ("wheel|{0}|{1}|0|{2}|{3}" -f $X, $Y, $Delta, $Modifiers)) +} + +function Send-SmokeAsciiText([string]$Text) { + if (Use-HeadedMailboxInput) { + [void](Write-HeadedMailboxLine ("text|{0}" -f $Text)) + return + } + if (Use-BareMetalInput) { + foreach ($ch in $Text.ToCharArray()) { + if ($ch -eq ' ') { + Send-SmokeSpace + Start-Sleep -Milliseconds 25 + continue + } + + $upper = [char]::ToUpperInvariant($ch) + Send-BareMetalKeyStroke -Code ([int][char]$upper) + Start-Sleep -Milliseconds 25 + } + return + } + + [SmokeProbeUser32]::SendUnicodeString($Text) +} + +function Get-BareMetalInputPath { + if (Use-BareMetalInput) { + return $env:LIGHTPANDA_BARE_METAL_INPUT + } + return $null +} + +function Write-BareMetalInputLine([string]$Line) { + $path = Get-BareMetalInputPath + if (-not $path) { + return $false + } + + $parent = Split-Path -Parent $path + if ($parent) { + New-Item -ItemType Directory -Force -Path $parent | Out-Null + } + + $encoding = [System.Text.UTF8Encoding]::new($false) + [System.IO.File]::AppendAllText($path, $Line + [Environment]::NewLine, $encoding) + return $true +} + +function Send-BareMetalKeyStroke([int]$Code, [int]$Modifiers = 0) { + [void](Write-BareMetalInputLine ("key|{0}|1|{1}" -f $Code, $Modifiers)) + [void](Write-BareMetalInputLine ("key|{0}|0|{1}" -f $Code, $Modifiers)) +} + +function Send-BareMetalPointerMove([int]$X, [int]$Y, [int]$Modifiers = 0) { + [void](Write-BareMetalInputLine ("move|{0}|{1}|{2}" -f $X, $Y, $Modifiers)) +} + +function Send-BareMetalPointerClick([int]$X, [int]$Y, [string]$Button = 'left', [int]$Modifiers = 0) { + Send-BareMetalPointerMove -X $X -Y $Y -Modifiers $Modifiers + [void](Write-BareMetalInputLine ("pointer|{0}|{1}|{2}|1|{3}" -f $X, $Y, $Button, $Modifiers)) + [void](Write-BareMetalInputLine ("pointer|{0}|{1}|{2}|0|{3}" -f $X, $Y, $Button, $Modifiers)) +} + +function Send-BareMetalWheel([int]$X, [int]$Y, [int]$Delta, [int]$Modifiers = 0) { + Send-BareMetalPointerMove -X $X -Y $Y -Modifiers $Modifiers + [void](Write-BareMetalInputLine ("wheel|0|{0}|{1}" -f $Delta, $Modifiers)) +} + +function Get-SmokeWindowTitle([IntPtr]$Hwnd) { + $builder = New-Object System.Text.StringBuilder 512 + [void][SmokeProbeUser32]::GetWindowTextW($Hwnd, $builder, $builder.Capacity) + return $builder.ToString() +} + +function Get-SmokeClientLParam([int]$X, [int]$Y) { + $xPart = [int]($X -band 0xFFFF) + $yPart = [int](($Y -band 0xFFFF) -shl 16) + return [IntPtr]($xPart -bor $yPart) +} + +function Show-SmokeWindow([IntPtr]$Hwnd) { + [void][SmokeProbeUser32]::ShowWindow($Hwnd, 5) + $HWND_TOPMOST = [IntPtr](-1) + $HWND_NOTOPMOST = [IntPtr](-2) + $SWP_NOMOVE = 0x0002 + $SWP_NOSIZE = 0x0001 + $SWP_SHOWWINDOW = 0x0040 + [void][SmokeProbeUser32]::SetWindowPos($Hwnd, $HWND_TOPMOST, 0, 0, 0, 0, ($SWP_NOMOVE -bor $SWP_NOSIZE -bor $SWP_SHOWWINDOW)) + [void][SmokeProbeUser32]::SetWindowPos($Hwnd, $HWND_NOTOPMOST, 0, 0, 0, 0, ($SWP_NOMOVE -bor $SWP_NOSIZE -bor $SWP_SHOWWINDOW)) + [void][SmokeProbeUser32]::SetActiveWindow($Hwnd) + [void][SmokeProbeUser32]::SetForegroundWindow($Hwnd) + Start-Sleep -Milliseconds 120 +} + +function Invoke-SmokeClientClickDirect([IntPtr]$Hwnd, [int]$X, [int]$Y) { + $WM_MOUSEMOVE = 0x0200 + $WM_LBUTTONDOWN = 0x0201 + $WM_LBUTTONUP = 0x0202 + $MK_LBUTTON = 0x0001 + $lParam = Get-SmokeClientLParam -X $X -Y $Y + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_MOUSEMOVE, [IntPtr]::Zero, $lParam) + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_LBUTTONDOWN, [IntPtr]$MK_LBUTTON, $lParam) + Start-Sleep -Milliseconds 30 + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_LBUTTONUP, [IntPtr]::Zero, $lParam) +} + +function Send-SmokeWindowText([IntPtr]$Hwnd, [string]$Text) { + $WM_CHAR = 0x0102 + foreach ($ch in $Text.ToCharArray()) { + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_CHAR, [IntPtr][int][char]$ch, [IntPtr]::Zero) + Start-Sleep -Milliseconds 20 + } +} + +function Send-SmokeWindowVirtualKey([IntPtr]$Hwnd, [int]$Vk) { + $WM_KEYDOWN = 0x0100 + $WM_KEYUP = 0x0101 + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_KEYDOWN, [IntPtr]$Vk, [IntPtr]::Zero) + Start-Sleep -Milliseconds 20 + [void][SmokeProbeUser32]::SendMessageW($Hwnd, $WM_KEYUP, [IntPtr]$Vk, [IntPtr]::Zero) +} + +function Send-SmokeWindowEnter([IntPtr]$Hwnd) { + Send-SmokeWindowVirtualKey -Hwnd $Hwnd -Vk 13 + [void][SmokeProbeUser32]::SendMessageW($Hwnd, 0x0102, [IntPtr]13, [IntPtr]::Zero) +} + +function Invoke-SmokeClientClick([IntPtr]$Hwnd, [int]$X, [int]$Y) { + $point = New-Object SmokeProbeUser32+POINT + $point.X = $X + $point.Y = $Y + if (Use-BareMetalInput) { + Send-BareMetalPointerClick -X $X -Y $Y -Button 'left' + return $point + } + if (Use-HeadedMailboxInput) { + Send-HeadedPointerClick -X $X -Y $Y -Button 'left' + return $point + } + + [void][SmokeProbeUser32]::ClientToScreen($Hwnd, [ref]$point) + [void][SmokeProbeUser32]::SetCursorPos($point.X, $point.Y) + Start-Sleep -Milliseconds 100 + [SmokeProbeUser32]::SendLeftDown() + Start-Sleep -Milliseconds 80 + [SmokeProbeUser32]::SendLeftUp() + return $point +} + +function Invoke-SmokeClientWheel([IntPtr]$Hwnd, [int]$X, [int]$Y, [int]$Delta) { + $point = New-Object SmokeProbeUser32+POINT + $point.X = $X + $point.Y = $Y + if (Use-BareMetalInput) { + Send-BareMetalWheel -X $X -Y $Y -Delta $Delta + return $point + } + if (Use-HeadedMailboxInput) { + Send-HeadedWheel -X $X -Y $Y -Delta $Delta + return $point + } + + [void][SmokeProbeUser32]::ClientToScreen($Hwnd, [ref]$point) + [void][SmokeProbeUser32]::SetCursorPos($point.X, $point.Y) + Start-Sleep -Milliseconds 100 + [SmokeProbeUser32]::SendMouseWheel($Delta) + return $point +} + +function Invoke-SmokeClientCtrlWheel([IntPtr]$Hwnd, [int]$X, [int]$Y, [int]$Delta) { + $point = New-Object SmokeProbeUser32+POINT + $point.X = $X + $point.Y = $Y + if (Use-BareMetalInput) { + Send-BareMetalWheel -X $X -Y $Y -Delta $Delta -Modifiers 2 + return $point + } + if (Use-HeadedMailboxInput) { + Send-HeadedWheel -X $X -Y $Y -Delta $Delta -Modifiers 2 + return $point + } + + [void][SmokeProbeUser32]::ClientToScreen($Hwnd, [ref]$point) + [void][SmokeProbeUser32]::SetCursorPos($point.X, $point.Y) + Start-Sleep -Milliseconds 100 + [SmokeProbeUser32]::SendCtrlWheel($Delta) + return $point +} + +function Send-SmokeCtrlA { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 65 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlA() +} + +function Send-SmokeCtrlF { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 70 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlF() +} + +function Send-SmokeCtrlD { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 68 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlD() +} + +function Send-SmokeCtrlH { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 72 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlH() +} + +function Send-SmokeCtrlJ { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 74 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlJ() +} + +function Send-SmokeCtrlL { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 76 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlL() +} + +function Send-SmokeCtrlComma { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 44 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlComma() +} + +function Send-SmokeCtrlT { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 84 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlT() +} + +function Send-SmokeCtrlW { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 87 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlW() +} + +function Send-SmokeCtrlTab { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 9 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlTab() +} + +function Send-SmokeCtrlShiftTab { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 9 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftTab() +} + +function Send-SmokeCtrlDigit([int]$Digit) { + if ($Digit -lt 1 -or $Digit -gt 9) { + throw "Digit must be between 1 and 9" + } + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code (48 + $Digit) -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlDigit([uint16](0x30 + $Digit)) +} + +function Send-SmokeCtrlPlus { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 43 -Modifiers 2 + return + } + [SmokeProbeUser32]::SendCtrlPlus() +} + +function Send-SmokeCtrlShiftP { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 80 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftP() +} + +function Send-SmokeCtrlShiftA { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 65 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftA() +} + +function Send-SmokeCtrlShiftB { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 66 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftB() +} + +function Send-SmokeCtrlShiftT { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 84 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftT() +} + +function Send-SmokeCtrlShiftD { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 68 -Modifiers 3 + return + } + [SmokeProbeUser32]::SendCtrlShiftD() +} + +function Send-SmokeCtrlAltH { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 72 -Modifiers 6 + return + } + [SmokeProbeUser32]::SendCtrlAltH() +} + +function Send-SmokeCtrlAltB { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 66 -Modifiers 6 + return + } + [SmokeProbeUser32]::SendCtrlAltB() +} + +function Send-SmokeCtrlAltJ { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 74 -Modifiers 6 + return + } + [SmokeProbeUser32]::SendCtrlAltJ() +} + +function Send-SmokeCtrlAltS { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 83 -Modifiers 6 + return + } + [SmokeProbeUser32]::SendCtrlAltS() +} + +function Send-SmokeAltHome { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 36 -Modifiers 4 + return + } + [SmokeProbeUser32]::SendAltHome() +} + +function Send-SmokeEnter { + if (Use-HeadedMailboxInput) { + Send-HeadedKeyStroke -Code 13 + return + } + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 13 + return + } + [SmokeProbeUser32]::SendEnter() +} + +function Send-SmokeSpace { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 32 + return + } + [SmokeProbeUser32]::SendSpace() +} + +function Send-SmokeEscape { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 27 + return + } + [SmokeProbeUser32]::SendEscape() +} + +function Send-SmokeF3 { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 114 + return + } + [SmokeProbeUser32]::SendF3() +} + +function Send-SmokeF5 { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 116 + return + } + [SmokeProbeUser32]::SendF5() +} + +function Send-SmokeShiftF3 { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 114 -Modifiers 1 + return + } + [SmokeProbeUser32]::SendShiftF3() +} + +function Send-SmokeTab { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 9 + return + } + [SmokeProbeUser32]::SendTab() +} + +function Send-SmokeUp { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 38 + return + } + [SmokeProbeUser32]::SendUp() +} + +function Send-SmokeDown { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 40 + return + } + [SmokeProbeUser32]::SendDown() +} + +function Send-SmokeLeft { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 37 + return + } + [SmokeProbeUser32]::SendVirtualKey([uint16]0x25) +} + +function Send-SmokeRight { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 39 + return + } + [SmokeProbeUser32]::SendVirtualKey([uint16]0x27) +} + +function Send-SmokeHome { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 36 + return + } + [SmokeProbeUser32]::SendHome() +} + +function Send-SmokeEnd { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 35 + return + } + [SmokeProbeUser32]::SendEnd() +} + +function Send-SmokePageUp { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 33 + return + } + [SmokeProbeUser32]::SendPageUp() +} + +function Send-SmokePageDown { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 34 + return + } + [SmokeProbeUser32]::SendPageDown() +} + +function Send-SmokeDelete { + if (Use-BareMetalInput) { + Send-BareMetalKeyStroke -Code 46 + return + } + [SmokeProbeUser32]::SendDelete() +} + +function Send-SmokeText([string]$Text) { + if (Use-BareMetalInput) { + foreach ($ch in $Text.ToCharArray()) { + $code = [int][char]$ch + if ($code -eq 10 -or $code -eq 13) { + Send-BareMetalKeyStroke -Code 13 + } else { + Send-BareMetalKeyStroke -Code $code + } + } + return + } + [SmokeProbeUser32]::SendUnicodeString($Text) +} diff --git a/tmp-browser-smoke/cookie-persistence/CookieProbeCommon.ps1 b/tmp-browser-smoke/cookie-persistence/CookieProbeCommon.ps1 new file mode 100644 index 000000000..4862abb20 --- /dev/null +++ b/tmp-browser-smoke/cookie-persistence/CookieProbeCommon.ps1 @@ -0,0 +1,155 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\cookie-persistence" +$script:BrowserExe = Join-Path $script:Repo "zig-out\bin\lightpanda.exe" + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-CookieProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + CookiesFile = Join-Path $appDataRoot "cookies-v1.txt" + SettingsFile = Join-Path $appDataRoot "browse-settings-v1.txt" + } +} + +function Seed-CookieProfile([string]$AppDataRoot) { + @" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Wait-CookieServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/seed.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-CookieServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "cookie_server.py"),"$Port" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Start-CookieBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Invoke-CookieAddressCommit([IntPtr]$Hwnd, [string]$Url) { + [void](Invoke-SmokeClientClick $Hwnd 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 120 + Send-SmokeText $Url + Start-Sleep -Milliseconds 120 + Send-SmokeEnter +} + +function Invoke-CookieAddressNavigate([IntPtr]$Hwnd, [int]$BrowserId, [string]$Url, [string]$Needle) { + Invoke-CookieAddressCommit $Hwnd $Url + return Wait-TabTitle $BrowserId $Needle 40 +} + +function Focus-CookieDocument([IntPtr]$Hwnd) { + [void](Invoke-SmokeClientClick $Hwnd 120 120) + Start-Sleep -Milliseconds 120 +} + +function Invoke-CookieSettingsClear([IntPtr]$Hwnd, [int]$TabCount = 8, [int]$PauseMs = 500) { + Focus-CookieDocument $Hwnd + for ($i = 0; $i -lt $TabCount; $i++) { + Send-SmokeTab + Start-Sleep -Milliseconds 120 + } + Send-SmokeEnter + Start-Sleep -Milliseconds $PauseMs +} + +function Read-CookieFileData([string]$CookieFile) { + if (-not (Test-Path $CookieFile)) { + return "" + } + return Get-Content $CookieFile -Raw +} + +function Wait-OwnedProbeProcessGone([int]$ProcessId, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + if (-not (Get-Process -Id $ProcessId -ErrorAction SilentlyContinue)) { + return $true + } + Start-Sleep -Milliseconds 150 + } + return $false +} + +function Wait-CookieFileMatch([string]$CookieFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-CookieFileData $CookieFile + if ($data -match $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Wait-CookieFileNoMatch([string]$CookieFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-CookieFileData $CookieFile + if ($data -notmatch $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Format-CookieProbeProcessMeta($Meta) { + if (-not $Meta) { + return $null + } + + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-CookieProbeResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + + if ($value -is [System.Collections.IDictionary]) { + Write-CookieProbeResult $value "$key." + continue + } + + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/cookie-persistence/chrome-cookie-clear-probe.ps1 b/tmp-browser-smoke/cookie-persistence/chrome-cookie-clear-probe.ps1 new file mode 100644 index 000000000..7b0de464c --- /dev/null +++ b/tmp-browser-smoke/cookie-persistence/chrome-cookie-clear-probe.ps1 @@ -0,0 +1,116 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\cookie-persistence\CookieProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-cookie-clear" +$app = Reset-CookieProfile $profileRoot +Seed-CookieProfile $app.AppDataRoot +$port = 8194 +$browserOneOut = Join-Path $Root "chrome-cookie-clear.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-cookie-clear.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-cookie-clear.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-cookie-clear.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-cookie-clear.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-cookie-clear.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$settingsOpened = $false +$clearInvoked = $false +$missingAfterClear = $false +$missingAfterRestart = $false +$cookieClearedOnDisk = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$cookieData = "" + +try { + $server = Start-CookieServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-CookieServer -Port $port + if (-not $ready) { throw "cookie server did not become ready" } + + $browserOne = Start-CookieBrowser -StartupUrl "http://127.0.0.1:$port/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "cookie clear run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "Cookie Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not load" } + + $titles.settings = Invoke-CookieAddressNavigate $hwndOne $browserOne.Id "browser://settings" "Browser Settings" + $settingsOpened = [bool]$titles.settings + if (-not $settingsOpened) { throw "browser://settings did not load" } + + Invoke-CookieSettingsClear $hwndOne + $titles.settings_after_clear = Wait-TabTitle $browserOne.Id "Browser Settings" 20 + $clearInvoked = [bool]$titles.settings_after_clear + if (-not $clearInvoked) { throw "settings clear action did not return to Browser Settings" } + + $cookieData = Wait-CookieFileNoMatch $app.CookiesFile "cookie\tlppersist\tok\t127.0.0.1\t/" + $cookieClearedOnDisk = [bool]$cookieData + if (-not $cookieClearedOnDisk) { throw "cookie persisted file still contains cleared cookie" } + + $titles.echo_missing = Invoke-CookieAddressNavigate $hwndOne $browserOne.Id "http://127.0.0.1:$port/echo.html" "Cookie Echo missing" + $missingAfterClear = [bool]$titles.echo_missing + if (-not $missingAfterClear) { throw "cookie echo after clear still showed cookie" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-CookieBrowser -StartupUrl "http://127.0.0.1:$port/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "cookie clear run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart_missing = Wait-TabTitle $browserTwo.Id "Cookie Echo missing" 40 + $missingAfterRestart = [bool]$titles.restart_missing + if (-not $missingAfterRestart) { throw "cookie remained present after restart" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $cookieData) { $cookieData = Read-CookieFileData $app.CookiesFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + settings_opened = $settingsOpened + clear_invoked = $clearInvoked + cookie_cleared_on_disk = $cookieClearedOnDisk + missing_after_clear = $missingAfterClear + missing_after_restart = $missingAfterRestart + titles = $titles + cookie_file = $cookieData + error = $failure + server_meta = Format-CookieProbeProcessMeta $serverMeta + browser_one_meta = Format-CookieProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-CookieProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-CookieProbeResult $result + + if ($failure -or -not $seedWorked -or -not $settingsOpened -or -not $clearInvoked -or -not $cookieClearedOnDisk -or -not $missingAfterClear -or -not $missingAfterRestart) { + exit 1 + } +} diff --git a/tmp-browser-smoke/cookie-persistence/chrome-cookie-cross-tab-probe.ps1 b/tmp-browser-smoke/cookie-persistence/chrome-cookie-cross-tab-probe.ps1 new file mode 100644 index 000000000..770d3a855 --- /dev/null +++ b/tmp-browser-smoke/cookie-persistence/chrome-cookie-cross-tab-probe.ps1 @@ -0,0 +1,82 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\cookie-persistence\CookieProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-cookie-cross-tab" +$app = Reset-CookieProfile $profileRoot +Seed-CookieProfile $app.AppDataRoot +$port = 8192 +$browserOut = Join-Path $Root "chrome-cookie-cross-tab.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-cookie-cross-tab.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-cookie-cross-tab.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-cookie-cross-tab.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$echoWorked = $false +$seedTabRetained = $false +$cookiePersisted = $false +$failure = $null +$titles = [ordered]@{} +$cookieData = "" + +try { + $server = Start-CookieServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-CookieServer -Port $port + if (-not $ready) { throw "cookie server did not become ready" } + + $browser = Start-CookieBrowser -StartupUrl "http://127.0.0.1:$port/seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "cookie cross-tab window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-TabTitle $browser.Id "Cookie Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not load" } + $cookieData = Wait-CookieFileMatch $app.CookiesFile "cookie\tlppersist\tok\t127.0.0.1\t/" + $cookiePersisted = [bool]$cookieData + if (-not $cookiePersisted) { throw "seed cookie did not settle to disk before cross-tab check" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-CookieAddressNavigate $hwnd $browser.Id "http://127.0.0.1:$port/echo.html" "Cookie Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "cookie echo in new tab did not see shared cookie" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-TabTitle $browser.Id "Cookie Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "seed tab was not preserved after cross-tab check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + cookie_persisted = $cookiePersisted + echo_worked = $echoWorked + seed_tab_retained = $seedTabRetained + cookie_file = if ($cookieData) { $cookieData } else { Read-CookieFileData $app.CookiesFile } + titles = $titles + error = $failure + server_meta = Format-CookieProbeProcessMeta $serverMeta + browser_meta = Format-CookieProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-CookieProbeResult $result + + if ($failure -or -not $seedWorked -or -not $cookiePersisted -or -not $echoWorked -or -not $seedTabRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/cookie-persistence/chrome-cookie-restart-probe.ps1 b/tmp-browser-smoke/cookie-persistence/chrome-cookie-restart-probe.ps1 new file mode 100644 index 000000000..2583eca06 --- /dev/null +++ b/tmp-browser-smoke/cookie-persistence/chrome-cookie-restart-probe.ps1 @@ -0,0 +1,98 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\cookie-persistence\CookieProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-cookie-restart" +$app = Reset-CookieProfile $profileRoot +Seed-CookieProfile $app.AppDataRoot +$port = 8193 +$browserOneOut = Join-Path $Root "chrome-cookie-restart.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-cookie-restart.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-cookie-restart.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-cookie-restart.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-cookie-restart.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-cookie-restart.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$persistedToDisk = $false +$restartWorked = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$cookieData = "" + +try { + $server = Start-CookieServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-CookieServer -Port $port + if (-not $ready) { throw "cookie server did not become ready" } + + $browserOne = Start-CookieBrowser -StartupUrl "http://127.0.0.1:$port/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "cookie restart run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "Cookie Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not load" } + + Start-Sleep -Milliseconds 700 + $cookieData = Read-CookieFileData $app.CookiesFile + $persistedToDisk = $cookieData -match "cookie\tlppersist\tok\t127.0.0.1\t/" + if (-not $persistedToDisk) { throw "cookie file was not persisted before restart" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-CookieBrowser -StartupUrl "http://127.0.0.1:$port/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "cookie restart run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart = Wait-TabTitle $browserTwo.Id "Cookie Echo ok" 40 + $restartWorked = [bool]$titles.restart + if (-not $restartWorked) { throw "restarted browser did not reuse persisted cookie" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $cookieData) { $cookieData = Read-CookieFileData $app.CookiesFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + persisted_to_disk = $persistedToDisk + restart_worked = $restartWorked + titles = $titles + cookie_file = $cookieData + error = $failure + server_meta = Format-CookieProbeProcessMeta $serverMeta + browser_one_meta = Format-CookieProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-CookieProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-CookieProbeResult $result + + if ($failure -or -not $seedWorked -or -not $persistedToDisk -or -not $restartWorked) { + exit 1 + } +} diff --git a/tmp-browser-smoke/cookie-persistence/cookie_server.py b/tmp-browser-smoke/cookie-persistence/cookie_server.py new file mode 100644 index 000000000..a8735d4f2 --- /dev/null +++ b/tmp-browser-smoke/cookie-persistence/cookie_server.py @@ -0,0 +1,75 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys + + +def html(title: str) -> bytes: + return ( + "" + f"{title}

{title}

" + ).encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + server_version = "CookieSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def do_GET(self): + if self.path == "/favicon.ico": + self.send_response(204) + self.end_headers() + return + + if self.path == "/seed.html": + body = html("Cookie Seeded - Lightpanda Browser") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Set-Cookie", "lppersist=ok; Path=/") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/echo.html": + cookie = self.headers.get("Cookie", "") + sys.stderr.write(f"ECHO_COOKIE {cookie!r}\n") + has_cookie = "lppersist=ok" in cookie + body = html( + "Cookie Echo ok - Lightpanda Browser" + if has_cookie + else "Cookie Echo missing - Lightpanda Browser" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = html("Not Found - Lightpanda Browser") + self.send_response(404) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + +def main() -> int: + port = 8192 + if len(sys.argv) > 1: + port = int(sys.argv[1]) + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/downloads/artifact.txt b/tmp-browser-smoke/downloads/artifact.txt new file mode 100644 index 000000000..6f1500806 --- /dev/null +++ b/tmp-browser-smoke/downloads/artifact.txt @@ -0,0 +1 @@ +download smoke payload diff --git a/tmp-browser-smoke/downloads/chrome-download-delete-probe.ps1 b/tmp-browser-smoke/downloads/chrome-download-delete-probe.ps1 new file mode 100644 index 000000000..a5f46c296 --- /dev/null +++ b/tmp-browser-smoke/downloads/chrome-download-delete-probe.ps1 @@ -0,0 +1,164 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\downloads" +$profileRoot = Join-Path $root "profile-download-delete" +$port = 8155 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$initialPng = Join-Path $root "chrome-download-delete.initial.png" +$browserOut = Join-Path $root "chrome-download-delete.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-download-delete.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-download-delete.server.stdout.txt" +$serverErr = Join-Path $root "chrome-download-delete.server.stderr.txt" + +Remove-Item $initialPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Remove-Item $profileRoot -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Path $profileRoot -Force | Out-Null + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Get-ColorBoundsInRegion([System.Drawing.Bitmap]$Bitmap, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = [Math]::Max(0, $MinY); $y -le [Math]::Min($MaxY, $Bitmap.Height - 1); $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le [Math]::Min($MaxX, $Bitmap.Width - 1); $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Wait-FileExists([string]$Path, [int]$Attempts = 60, [int]$DelayMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $DelayMs + if ((Test-Path $Path) -and ((Get-Item $Path).Length -gt 0)) { + return $true + } + } + return $false +} + +function Wait-FileMissing([string]$Path, [int]$Attempts = 60, [int]$DelayMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $DelayMs + if (-not (Test-Path $Path)) { + return $true + } + } + return $false +} + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +$env:LIGHTPANDA_BARE_METAL_INPUT = Join-Path $profileRoot "lightpanda\bare-metal-input-v1.txt" +$downloadsDir = Join-Path $profileRoot "lightpanda\downloads" +$downloadsFile = Join-Path $profileRoot "lightpanda\downloads-v1.txt" +$downloadedFile = Join-Path $downloadsDir "example-download.txt" + +New-Item -ItemType Directory -Path $downloadsDir -Force | Out-Null +[System.IO.File]::WriteAllText($downloadedFile, "download smoke payload`n", [System.Text.UTF8Encoding]::new($false)) +[System.IO.File]::WriteAllText( + $downloadsFile, + "2`t23`t23`t1`texample-download.txt`t$downloadedFile`thttp://127.0.0.1:$port/artifact.txt`t`n", + [System.Text.UTF8Encoding]::new($false) +) + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$downloadWorked = $false +$metadataWorked = $false +$deleteWorked = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "download delete probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","browser://downloads","--window_width","960","--window_height","640","--screenshot_png",$initialPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "download delete probe window handle not found" } + $null = Wait-TabTitle $browser.Id "Browser Downloads" + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $initialPng) -and ((Get-Item $initialPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "download delete probe screenshot did not become ready" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 750 + + $downloadWorked = (Test-Path $downloadedFile) -and ((Get-Item $downloadedFile).Length -gt 0) + $metadataWorked = (Test-Path $downloadsFile) -and ((Get-Item $downloadsFile).Length -gt 0) + if (-not $downloadWorked) { throw "download delete probe did not seed the file" } + if (-not $metadataWorked) { throw "download delete probe did not seed the metadata file" } + + [void](Write-BareMetalInputLine "command|download_remove|0") + [void](Invoke-SmokeClientClick $hwnd 500 300) + $deleteWorked = Wait-FileMissing $downloadedFile 30 100 + if (-not $deleteWorked) { throw "download delete probe did not remove the file" } + + if ((Test-Path $downloadsFile) -and ((Get-Content $downloadsFile -Raw) -match "example-download\.txt")) { + throw "download delete probe still found saved metadata" + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + download_worked = $downloadWorked + delete_worked = $deleteWorked + downloaded_file = $downloadedFile + downloads_file = $downloadsFile + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/downloads/chrome-download-probe.ps1 b/tmp-browser-smoke/downloads/chrome-download-probe.ps1 new file mode 100644 index 000000000..8493e7e13 --- /dev/null +++ b/tmp-browser-smoke/downloads/chrome-download-probe.ps1 @@ -0,0 +1,137 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\downloads" +$profileRoot = Join-Path $root "profile-download" +$port = 8154 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$initialPng = Join-Path $root "chrome-download.initial.png" +$browserOut = Join-Path $root "chrome-download.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-download.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-download.server.stdout.txt" +$serverErr = Join-Path $root "chrome-download.server.stderr.txt" + +Remove-Item $initialPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Remove-Item $profileRoot -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Path $profileRoot -Force | Out-Null + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Wait-FileExists([string]$Path, [int]$Attempts = 60, [int]$DelayMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $DelayMs + if ((Test-Path $Path) -and ((Get-Item $Path).Length -gt 0)) { + return $true + } + } + return $false +} + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +$env:LIGHTPANDA_BARE_METAL_INPUT = Join-Path $profileRoot "lightpanda\bare-metal-input-v1.txt" +$downloadsDir = Join-Path $profileRoot "lightpanda\downloads" +$downloadsFile = Join-Path $profileRoot "lightpanda\downloads-v1.txt" +$downloadedFile = Join-Path $downloadsDir "example-download.txt" + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$downloadWorked = $false +$metadataWorked = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "download probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640","--screenshot_png",$initialPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "download probe window handle not found" } + $null = Wait-TabTitle $browser.Id "Download Smoke" + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $initialPng) -and ((Get-Item $initialPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "download probe screenshot did not become ready" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + + $bmp = [System.Drawing.Bitmap]::new($initialPng) + try { + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $blue.min_x) { throw "download probe could not find link bounds" } + + $linkX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $linkY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + [void](Invoke-SmokeClientClick $hwnd $linkX $linkY) + + $downloadWorked = Wait-FileExists $downloadedFile + if (-not $downloadWorked) { throw "downloaded file was not created" } + + $metadataWorked = Wait-FileExists $downloadsFile + if (-not $metadataWorked) { throw "downloads state file was not created" } + + $content = Get-Content $downloadedFile -Raw + if ($content -ne "download smoke payload`n" -and $content -ne "download smoke payload") { + throw "downloaded file content mismatch" + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + download_worked = $downloadWorked + metadata_worked = $metadataWorked + downloaded_file = $downloadedFile + downloads_file = $downloadsFile + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/downloads/index.html b/tmp-browser-smoke/downloads/index.html new file mode 100644 index 000000000..a1ecdd1d6 --- /dev/null +++ b/tmp-browser-smoke/downloads/index.html @@ -0,0 +1,11 @@ + + + + + Download Smoke + + +

Download Smoke

+

Download Example File

+ + diff --git a/tmp-browser-smoke/fetch-abort/FetchAbortProbeCommon.ps1 b/tmp-browser-smoke/fetch-abort/FetchAbortProbeCommon.ps1 new file mode 100644 index 000000000..043825ec2 --- /dev/null +++ b/tmp-browser-smoke/fetch-abort/FetchAbortProbeCommon.ps1 @@ -0,0 +1,102 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\fetch-abort" +$script:BrowserExe = Join-Path $script:Repo "zig-out\bin\lightpanda.exe" + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-FetchAbortProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + } +} + +function Seed-FetchAbortProfile([string]$AppDataRoot) { +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Wait-FetchAbortServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/healthz" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-FetchAbortServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "fetch_abort_server.py"),"$Port" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Start-FetchAbortBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Read-FetchAbortServerLog([string]$Path) { + if (-not (Test-Path $Path)) { + return "" + } + return Get-Content -Path $Path -Raw +} + +function Wait-FetchAbortServerLogMatch([string]$Path, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-FetchAbortServerLog $Path + if ($data -match $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Format-FetchAbortProcessMeta($Meta) { + if (-not $Meta) { + return $null + } + + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-FetchAbortResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + + if ($value -is [System.Collections.IDictionary]) { + Write-FetchAbortResult $value "$key." + continue + } + + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/fetch-abort/chrome-fetch-abort-probe.ps1 b/tmp-browser-smoke/fetch-abort/chrome-fetch-abort-probe.ps1 new file mode 100644 index 000000000..23e7afffb --- /dev/null +++ b/tmp-browser-smoke/fetch-abort/chrome-fetch-abort-probe.ps1 @@ -0,0 +1,71 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\fetch-abort\FetchAbortProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-fetch-abort" +$app = Reset-FetchAbortProfile $profileRoot +Seed-FetchAbortProfile $app.AppDataRoot +$port = 8463 +$pageUrl = "http://127.0.0.1:$port/page.html" +$browserOut = Join-Path $Root "chrome-fetch-abort.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-fetch-abort.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-fetch-abort.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-fetch-abort.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$titleReady = $false +$sawSlowStart = $false +$sawSlowAbort = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-FetchAbortServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-FetchAbortServer -Port $port + if (-not $ready) { throw "fetch abort server did not become ready" } + + $browser = Start-FetchAbortBrowser -StartupUrl $pageUrl -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "fetch abort window handle not found" } + Show-SmokeWindow $hwnd + + $titles.final = Wait-TabTitle $browser.Id "Fetch Abort Ready" 60 + $titleReady = [bool]$titles.final + if (-not $titleReady) { + $titles.fail = Wait-TabTitle $browser.Id "Fetch Abort" 5 + throw "fetch abort page did not reach the ready title" + } + + $startLog = Wait-FetchAbortServerLogMatch -Path $serverErr -Pattern "SLOW_START" -Attempts 20 + $abortLog = Wait-FetchAbortServerLogMatch -Path $serverErr -Pattern "SLOW_ABORTED" -Attempts 30 + $sawSlowStart = $null -ne $startLog + $sawSlowAbort = $null -ne $abortLog + if (-not $sawSlowStart) { throw "server did not observe slow request start" } + if (-not $sawSlowAbort) { throw "server did not observe slow request abort" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_ready = $titleReady + saw_slow_start = $sawSlowStart + saw_slow_abort = $sawSlowAbort + titles = $titles + error = $failure + server_meta = Format-FetchAbortProcessMeta $serverMeta + browser_meta = Format-FetchAbortProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-FetchAbortResult $result + if ($failure -or -not $titleReady -or -not $sawSlowStart -or -not $sawSlowAbort) { exit 1 } +} diff --git a/tmp-browser-smoke/fetch-abort/fetch_abort_server.py b/tmp-browser-smoke/fetch-abort/fetch_abort_server.py new file mode 100644 index 000000000..5483d04a4 --- /dev/null +++ b/tmp-browser-smoke/fetch-abort/fetch_abort_server.py @@ -0,0 +1,98 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys +import time + + +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8463 + + +def html(title: str, body: str) -> bytes: + return ( + "" + f"{title}{body}" + ).encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + server_version = "FetchAbortSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def _write_bytes(self, status: int, body: bytes, content_type: str = "text/html; charset=utf-8"): + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self): + if self.path == "/healthz": + self._write_bytes(200, b"ok", "text/plain; charset=utf-8") + return + + if self.path == "/page.html": + body = html( + "Fetch Abort Loading - Lightpanda Browser", + """ + +

fetch abort

+""", + ) + self._write_bytes(200, body) + return + + if self.path == "/slow": + sys.stderr.write("SLOW_START\n") + chunk = b"x" * 256 + chunk_count = 64 + total_len = len(chunk) * chunk_count + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(total_len)) + self.end_headers() + try: + for _ in range(chunk_count): + time.sleep(0.1) + self.wfile.write(chunk) + self.wfile.flush() + sys.stderr.write("SLOW_COMPLETE\n") + except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError): + sys.stderr.write("SLOW_ABORTED\n") + return + + self._write_bytes(404, html("Not Found - Lightpanda Browser", "

Not Found

")) + + +def main() -> int: + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/fetch-credentials/FetchCredentialsProbeCommon.ps1 b/tmp-browser-smoke/fetch-credentials/FetchCredentialsProbeCommon.ps1 new file mode 100644 index 000000000..cde555b12 --- /dev/null +++ b/tmp-browser-smoke/fetch-credentials/FetchCredentialsProbeCommon.ps1 @@ -0,0 +1,75 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\fetch-credentials" +$script:BrowserExe = Join-Path $script:Repo "zig-out\bin\lightpanda.exe" + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-FetchProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ AppDataRoot = $appDataRoot; DownloadsDir = $downloadsDir } +} + +function Seed-FetchProfile([string]$AppDataRoot) { +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Start-FetchServer([int]$Port, [int]$PeerPort, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "fetch_server.py"),"$Port","$PeerPort" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Wait-FetchServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/healthz" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-FetchBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Format-FetchProbeProcessMeta($Meta) { + if (-not $Meta) { return $null } + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-FetchProbeResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + if ($value -is [System.Collections.IDictionary]) { + Write-FetchProbeResult $value "$key." + continue + } + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/fetch-credentials/chrome-fetch-credentials-probe.ps1 b/tmp-browser-smoke/fetch-credentials/chrome-fetch-credentials-probe.ps1 new file mode 100644 index 000000000..39eabb73e --- /dev/null +++ b/tmp-browser-smoke/fetch-credentials/chrome-fetch-credentials-probe.ps1 @@ -0,0 +1,70 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\fetch-credentials\FetchCredentialsProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-fetch-credentials" +$app = Reset-FetchProfile $profileRoot +Seed-FetchProfile $app.AppDataRoot +$pagePort = 8423 +$crossPort = 8424 +$pageUrl = "http://fetch%20user:p%40ss@127.0.0.1:$pagePort/page.html" +$browserOut = Join-Path $Root "chrome-fetch-credentials.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-fetch-credentials.browser.stderr.txt" +$pageServerOut = Join-Path $Root "chrome-fetch-credentials.page.server.stdout.txt" +$pageServerErr = Join-Path $Root "chrome-fetch-credentials.page.server.stderr.txt" +$crossServerOut = Join-Path $Root "chrome-fetch-credentials.cross.server.stdout.txt" +$crossServerErr = Join-Path $Root "chrome-fetch-credentials.cross.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$pageServerOut,$pageServerErr,$crossServerOut,$crossServerErr -Force -ErrorAction SilentlyContinue + +$pageServer = $null +$crossServer = $null +$browser = $null +$ready = $false +$titleReady = $false +$failure = $null +$titles = [ordered]@{} + +try { + $pageServer = Start-FetchServer -Port $pagePort -PeerPort $crossPort -Stdout $pageServerOut -Stderr $pageServerErr + $crossServer = Start-FetchServer -Port $crossPort -PeerPort $pagePort -Stdout $crossServerOut -Stderr $crossServerErr + $ready = (Wait-FetchServer -Port $pagePort) -and (Wait-FetchServer -Port $crossPort) + if (-not $ready) { throw "fetch credential servers did not become ready" } + + $browser = Start-FetchBrowser -StartupUrl $pageUrl -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "fetch credentials window handle not found" } + Show-SmokeWindow $hwnd + + $titles.final = Wait-TabTitle $browser.Id "Fetch Credentials Ready" 50 + $titleReady = [bool]$titles.final + if (-not $titleReady) { + $titles.fail = Wait-TabTitle $browser.Id "Fetch Credentials" 5 + throw "fetch credentials page did not reach the ready title" + } +} catch { + $failure = $_.Exception.Message +} finally { + $pageServerMeta = Stop-OwnedProbeProcess $pageServer + $crossServerMeta = Stop-OwnedProbeProcess $crossServer + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $pageServerGone = if ($pageServer) { -not (Get-Process -Id $pageServer.Id -ErrorAction SilentlyContinue) } else { $true } + $crossServerGone = if ($crossServer) { -not (Get-Process -Id $crossServer.Id -ErrorAction SilentlyContinue) } else { $true } + $result = [ordered]@{ + page_server_pid = if ($pageServer) { $pageServer.Id } else { 0 } + cross_server_pid = if ($crossServer) { $crossServer.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_ready = $titleReady + titles = $titles + error = $failure + page_server_meta = Format-FetchProbeProcessMeta $pageServerMeta + cross_server_meta = Format-FetchProbeProcessMeta $crossServerMeta + browser_meta = Format-FetchProbeProcessMeta $browserMeta + browser_gone = $browserGone + page_server_gone = $pageServerGone + cross_server_gone = $crossServerGone + } + Write-FetchProbeResult $result + if ($failure -or -not $titleReady) { exit 1 } +} diff --git a/tmp-browser-smoke/fetch-credentials/fetch_server.py b/tmp-browser-smoke/fetch-credentials/fetch_server.py new file mode 100644 index 000000000..6b5cd9e54 --- /dev/null +++ b/tmp-browser-smoke/fetch-credentials/fetch_server.py @@ -0,0 +1,136 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import urlparse, parse_qs +import json +import sys + +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8423 +PEER_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else PORT + 1 +EXPECTED_AUTH = "Basic ZmV0Y2ggdXNlcjpwQHNz" + + +def html(title: str, body: str) -> bytes: + return ( + "" + f"{title}{body}" + ).encode("utf-8") + + +def cookie_value(headers): + raw = headers.get("Cookie", "") + for part in raw.split(";"): + part = part.strip() + if part.startswith("lpfetch="): + return part.split("=", 1)[1] + return "" + + +class Handler(BaseHTTPRequestHandler): + server_version = "FetchCredentialsSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def _write_bytes(self, status: int, body: bytes, content_type: str = "text/html; charset=utf-8", cors: bool = False): + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + if cors: + self.send_header("Access-Control-Allow-Origin", f"http://127.0.0.1:{PORT}") + self.send_header("Access-Control-Allow-Credentials", "true") + self.end_headers() + self.wfile.write(body) + + def do_GET(self): + parsed = urlparse(self.path) + path = parsed.path + query = parse_qs(parsed.query) + + if path == "/healthz": + self._write_bytes(200, b"ok", "text/plain; charset=utf-8") + return + + if path == "/page.html": + body = html( + "Fetch Credentials Loading - Lightpanda Browser", + f""" + +

fetch credentials

+""", + ) + self._write_bytes(200, body) + return + + if path == "/echo": + case = query.get("case", [""])[0] + headers = self.headers + referer = headers.get("Referer", "") + authorization = headers.get("Authorization", "") + cookie = cookie_value(headers) + expected_page_port = PORT + if case.startswith("cross-"): + expected_page_port = PEER_PORT + expected_referer = f"http://127.0.0.1:{expected_page_port}/page.html" + allowed = False + if case == "same-default": + allowed = (cookie == "ok" and authorization == EXPECTED_AUTH and referer == expected_referer) + elif case == "same-omit": + allowed = (cookie == "" and authorization == "" and referer == expected_referer) + elif case == "cross-same": + allowed = (cookie == "" and authorization == "" and referer == expected_referer) + elif case == "cross-include": + allowed = (cookie == "ok" and authorization == "" and referer == expected_referer) + sys.stderr.write( + json.dumps({ + "case": case, + "cookie": cookie, + "authorization": authorization, + "referer": referer, + "allowed": allowed, + "port": self.server.server_port, + }) + "\n" + ) + body = json.dumps({ + "case": case, + "cookie": cookie, + "authorization": authorization, + "referer": referer, + "allowed": allowed, + }).encode("utf-8") + self._write_bytes(200, body, "application/json; charset=utf-8", cors=(self.server.server_port == PEER_PORT or PORT != PEER_PORT)) + return + + self._write_bytes(404, html("Not Found - Lightpanda Browser", "

Not Found

")) + + +def main() -> int: + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/file-upload/FileUploadProbeCommon.ps1 b/tmp-browser-smoke/file-upload/FileUploadProbeCommon.ps1 new file mode 100644 index 000000000..763b76672 --- /dev/null +++ b/tmp-browser-smoke/file-upload/FileUploadProbeCommon.ps1 @@ -0,0 +1,210 @@ +$script:Repo = 'C:\Users\adyba\src\lightpanda-browser' +$script:Root = Join-Path $script:Repo 'tmp-browser-smoke\file-upload' +$script:BrowserExe = Join-Path $script:Repo 'zig-out\bin\lightpanda.exe' + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +if (-not ('SmokeProbeWindowEnum' -as [type])) { + Add-Type @" +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +public static class SmokeProbeWindowEnum { + public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetWindowTextW(IntPtr hWnd, StringBuilder text, int count); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetClassNameW(IntPtr hWnd, StringBuilder text, int count); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + public static IntPtr FindDialogWindow(uint processId, string titlePrefix) { + IntPtr found = IntPtr.Zero; + EnumWindows(delegate (IntPtr hWnd, IntPtr lParam) { + if (!IsWindowVisible(hWnd)) { + return true; + } + uint candidatePid; + GetWindowThreadProcessId(hWnd, out candidatePid); + if (candidatePid != processId) { + return true; + } + var classBuilder = new StringBuilder(128); + GetClassNameW(hWnd, classBuilder, classBuilder.Capacity); + if (!String.Equals(classBuilder.ToString(), "#32770", StringComparison.Ordinal)) { + return true; + } + var titleBuilder = new StringBuilder(512); + GetWindowTextW(hWnd, titleBuilder, titleBuilder.Capacity); + if (!String.IsNullOrEmpty(titlePrefix) && !titleBuilder.ToString().StartsWith(titlePrefix, StringComparison.OrdinalIgnoreCase)) { + return true; + } + found = hWnd; + return false; + }, IntPtr.Zero); + return found; + } +} +"@ +} + +function Reset-FileUploadProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot 'lightpanda' + $downloadsDir = Join-Path $appDataRoot 'downloads' + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + } +} + +function Start-FileUploadServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath 'python' -ArgumentList (Join-Path $script:Root 'upload_server.py'),$Port -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Wait-FileUploadServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/ping" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-FileUploadBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList 'browse',$StartupUrl,'--window_width','960','--window_height','640' -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Wait-UploadDialogWindow([int]$ProcessId, [int]$Attempts = 40, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + $hwnd = [SmokeProbeWindowEnum]::FindDialogWindow([uint32]$ProcessId, 'Select file') + if ($hwnd -ne [IntPtr]::Zero) { + return $hwnd + } + } + return [IntPtr]::Zero +} + +function Wait-UploadDialogClosed([int]$ProcessId, [int]$Attempts = 40, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + $hwnd = [SmokeProbeWindowEnum]::FindDialogWindow([uint32]$ProcessId, 'Select file') + if ($hwnd -eq [IntPtr]::Zero) { + return $true + } + } + return $false +} + +function Wait-FileUploadTitle([int]$ProcessId, [string]$Needle, [int]$Attempts = 40) { + return Wait-TabTitle $ProcessId $Needle $Attempts +} + +function Wait-FileUploadLogNeedle([string]$LogPath, [string]$Needle, [int]$Attempts = 40, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + if ((Test-Path $LogPath) -and ((Get-Content $LogPath -Raw) -like "*$Needle*")) { + return $true + } + } + return $false +} + +function Wait-FileUploadFileExists([string]$Path, [int]$Attempts = 40, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + if (Test-Path -LiteralPath $Path) { + return $true + } + } + return $false +} + +function Invoke-FileUploadOpenDialog([IntPtr]$BrowserHwnd, [int]$BrowserPid) { + Show-SmokeWindow $BrowserHwnd + Start-Sleep -Milliseconds 150 + [void](Invoke-SmokeClientClick $BrowserHwnd 180 222) + Start-Sleep -Milliseconds 120 + $dialog = Wait-UploadDialogWindow $BrowserPid 10 150 + if ($dialog -ne [IntPtr]::Zero) { + return $dialog + } + + Show-SmokeWindow $BrowserHwnd + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $dialog = Wait-UploadDialogWindow $BrowserPid 20 150 + return $dialog +} + +function Invoke-FileUploadChoosePath([IntPtr]$BrowserHwnd, [int]$BrowserPid, [string]$Path) { + $dialog = Invoke-FileUploadOpenDialog $BrowserHwnd $BrowserPid + if ($dialog -eq [IntPtr]::Zero) { + throw 'file chooser dialog did not appear' + } + Show-SmokeWindow $dialog + Start-Sleep -Milliseconds 180 + Send-SmokeText $Path + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + if (-not (Wait-UploadDialogClosed $BrowserPid 40 150)) { + throw 'file chooser dialog did not close after selection' + } +} + +function Invoke-FileUploadChoosePaths([IntPtr]$BrowserHwnd, [int]$BrowserPid, [string[]]$Paths) { + if (-not $Paths -or $Paths.Count -lt 2) { + throw 'Invoke-FileUploadChoosePaths requires at least two paths' + } + $dialog = Invoke-FileUploadOpenDialog $BrowserHwnd $BrowserPid + if ($dialog -eq [IntPtr]::Zero) { + throw 'file chooser dialog did not appear' + } + Show-SmokeWindow $dialog + Start-Sleep -Milliseconds 180 + $joined = ($Paths | ForEach-Object { '"' + $_ + '"' }) -join ' ' + Send-SmokeText $joined + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + if (-not (Wait-UploadDialogClosed $BrowserPid 40 150)) { + throw 'file chooser dialog did not close after multi-selection' + } +} + +function Invoke-FileUploadCancel([IntPtr]$BrowserHwnd, [int]$BrowserPid) { + $dialog = Invoke-FileUploadOpenDialog $BrowserHwnd $BrowserPid + if ($dialog -eq [IntPtr]::Zero) { + throw 'file chooser dialog did not appear' + } + Show-SmokeWindow $dialog + Start-Sleep -Milliseconds 180 + Send-SmokeEscape + if (-not (Wait-UploadDialogClosed $BrowserPid 40 150)) { + throw 'file chooser dialog did not close after cancel' + } +} + +function Invoke-FileUploadSubmit([IntPtr]$BrowserHwnd) { + Show-SmokeWindow $BrowserHwnd + Start-Sleep -Milliseconds 150 + [void](Invoke-SmokeClientClick $BrowserHwnd 120 256) + Start-Sleep -Milliseconds 120 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-attachment-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-attachment-probe.ps1 new file mode 100644 index 000000000..fbcf4118c --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-attachment-probe.ps1 @@ -0,0 +1,101 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-attachment' +$port = 8167 +$browserOut = Join-Path $root 'upload-attachment.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-attachment.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-attachment.server.stdout.txt' +$serverErr = Join-Path $root 'upload-attachment.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$samplePath = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$server = $null +$browser = $null +$ready = $false +$selectedWorked = $false +$downloadWorked = $false +$downloadsPageWorked = $false +$serverSawUpload = $false +$failure = $null +$titleBefore = $null +$titleAfterSubmit = $null +$titleDownloads = $null + +try { + $profile = Reset-FileUploadProfile $profileRoot + $downloadPath = Join-Path $profile.DownloadsDir 'uploaded-sample-upload.txt' + + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload attachment server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload-attachment.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload attachment browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Attachment Smoke' 40 + if (-not $titleBefore) { throw 'file upload attachment page did not load' } + + Invoke-FileUploadChoosePath $hwnd $browser.Id $samplePath + $selectedWorked = $true + + Invoke-FileUploadSubmit $hwnd + $serverSawUpload = Wait-FileUploadLogNeedle $serverErr 'UPLOAD_ATTACHMENT files=1' 40 200 + if (-not $serverSawUpload) { throw 'attachment upload server did not receive the selected file' } + if (-not (Wait-FileUploadFileExists $downloadPath 40 200)) { + throw 'attachment upload did not create the expected downloaded file' + } + $downloadWorked = $true + + $payload = Get-Content -LiteralPath $downloadPath -Raw + if ($payload -notmatch 'primary upload payload from sample one') { + throw 'attachment upload download payload was not preserved' + } + + $titleAfterSubmit = Wait-FileUploadTitle $browser.Id 'File Upload Attachment Smoke' 20 + if (-not $titleAfterSubmit) { + throw 'source page was not restored after attachment upload' + } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlJ + $titleDownloads = Wait-FileUploadTitle $browser.Id 'Downloads' 20 + if (-not $titleDownloads) { + throw 'downloads page did not open after attachment upload' + } + $downloadsPageWorked = $true +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_after_submit = $titleAfterSubmit + title_downloads = $titleDownloads + selected_worked = $selectedWorked + download_worked = $downloadWorked + downloads_page_worked = $downloadsPageWorked + server_saw_upload = $serverSawUpload + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-cancel-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-cancel-probe.ps1 new file mode 100644 index 000000000..338fae742 --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-cancel-probe.ps1 @@ -0,0 +1,73 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-cancel' +$port = 8163 +$browserOut = Join-Path $root 'upload-cancel.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-cancel.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-cancel.server.stdout.txt' +$serverErr = Join-Path $root 'upload-cancel.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$canceledWorked = $false +$serverSawNoUpload = $false +$failure = $null +$titleBefore = $null +$titleAfterCancel = $null + +try { + Reset-FileUploadProfile $profileRoot | Out-Null + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Smoke' 40 + if (-not $titleBefore) { throw 'file upload page did not load' } + + Invoke-FileUploadCancel $hwnd $browser.Id + Start-Sleep -Milliseconds 400 + $titleAfterCancel = Get-SmokeWindowTitle $hwnd + $canceledWorked = $titleAfterCancel -like 'File Upload Smoke*' + if (-not $canceledWorked) { throw 'canceling the chooser changed the page state' } + + Start-Sleep -Milliseconds 600 + $log = if (Test-Path $serverErr) { Get-Content $serverErr -Raw } else { '' } + $serverSawNoUpload = $log -notmatch 'POST /upload ' + if (-not $serverSawNoUpload) { throw 'chooser cancel still triggered an upload request' } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_after_cancel = $titleAfterCancel + canceled_worked = $canceledWorked + server_saw_no_upload = $serverSawNoUpload + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} \ No newline at end of file diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-multiple-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-multiple-probe.ps1 new file mode 100644 index 000000000..cf1a64364 --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-multiple-probe.ps1 @@ -0,0 +1,88 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-multiple' +$port = 8169 +$browserOut = Join-Path $root 'upload-multiple.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-multiple.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-multiple.server.stdout.txt' +$serverErr = Join-Path $root 'upload-multiple.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$samplePathA = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$samplePathB = (Resolve-Path (Join-Path $root 'sample-upload-second.txt')).Path +$server = $null +$browser = $null +$ready = $false +$selectedWorked = $false +$submittedWorked = $false +$serverSawUpload = $false +$failure = $null +$titleBefore = $null +$titleAfterSubmit = $null +$log = '' + +try { + Reset-FileUploadProfile $profileRoot | Out-Null + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload-multiple.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Multiple Smoke' 40 + if (-not $titleBefore) { throw 'file upload multiple page did not load' } + + Invoke-FileUploadChoosePaths $hwnd $browser.Id @($samplePathA, $samplePathB) + $selectedWorked = $true + + Invoke-FileUploadSubmit $hwnd + $titleAfterSubmit = Wait-FileUploadTitle $browser.Id 'Upload Submitted sample-upload.txt' 40 + $serverSawUpload = Wait-FileUploadLogNeedle $serverErr 'UPLOAD files=2' 40 200 + if ((-not $titleAfterSubmit) -or (-not $serverSawUpload)) { + throw 'multipart multi-file upload did not complete' + } + + $log = if (Test-Path $serverErr) { Get-Content $serverErr -Raw } else { '' } + if ($log -notmatch 'sample-upload\.txt:38:primary upload payload from sample one') { + throw 'server log did not include the first uploaded file' + } + if ($log -notmatch 'sample-upload-second\.txt:42:replacement upload payload from sample two') { + throw 'server log did not include the second uploaded file' + } + $submittedWorked = $true +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + $serverCommand = if ($serverMeta) { $serverMeta.CommandLine } else { $null } + $browserCommand = if ($browserMeta) { $browserMeta.CommandLine } else { $null } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_after_submit = $titleAfterSubmit + selected_worked = $selectedWorked + submitted_worked = $submittedWorked + server_saw_upload = $serverSawUpload + error = $failure + server_command = $serverCommand + browser_command = $browserCommand + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-replace-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-replace-probe.ps1 new file mode 100644 index 000000000..574291441 --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-replace-probe.ps1 @@ -0,0 +1,81 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-replace' +$port = 8164 +$browserOut = Join-Path $root 'upload-replace.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-replace.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-replace.server.stdout.txt' +$serverErr = Join-Path $root 'upload-replace.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$firstPath = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$secondPath = (Resolve-Path (Join-Path $root 'sample-upload-second.txt')).Path +$server = $null +$browser = $null +$ready = $false +$firstWorked = $false +$replaceWorked = $false +$submittedWorked = $false +$failure = $null +$titleAfterSubmit = $null + +try { + Reset-FileUploadProfile $profileRoot | Out-Null + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload browser window handle not found' } + if (-not (Wait-FileUploadTitle $browser.Id 'File Upload Smoke' 40)) { throw 'file upload page did not load' } + + Invoke-FileUploadChoosePath $hwnd $browser.Id $firstPath + $firstWorked = $true + + Invoke-FileUploadChoosePath $hwnd $browser.Id $secondPath + $replaceWorked = $true + + Invoke-FileUploadSubmit $hwnd + $titleAfterSubmit = Wait-FileUploadTitle $browser.Id 'Upload Submitted sample-upload-second.txt' 40 + if (-not $titleAfterSubmit) { throw 'replacement upload did not submit the second file' } + + $log = if (Test-Path $serverErr) { Get-Content $serverErr -Raw } else { '' } + if ($log -notmatch 'UPLOAD files=1') { + throw 'upload server did not receive the replacement file name' + } + if ($log -notmatch 'sample-upload-second\.txt:42:replacement upload payload from sample two') { + throw 'upload server did not receive the replacement file payload' + } + $submittedWorked = $true +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_after_submit = $titleAfterSubmit + first_worked = $firstWorked + replace_worked = $replaceWorked + submitted_worked = $submittedWorked + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-submit-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-submit-probe.ps1 new file mode 100644 index 000000000..16fb92a88 --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-submit-probe.ps1 @@ -0,0 +1,81 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-submit' +$port = 8162 +$browserOut = Join-Path $root 'upload-submit.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-submit.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-submit.server.stdout.txt' +$serverErr = Join-Path $root 'upload-submit.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$samplePath = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$server = $null +$browser = $null +$ready = $false +$selectedWorked = $false +$submittedWorked = $false +$serverSawUpload = $false +$failure = $null +$titleBefore = $null +$titleAfterSubmit = $null + +try { + Reset-FileUploadProfile $profileRoot | Out-Null + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Smoke' 40 + if (-not $titleBefore) { throw 'file upload page did not load' } + + Invoke-FileUploadChoosePath $hwnd $browser.Id $samplePath + $selectedWorked = $true + + Invoke-FileUploadSubmit $hwnd + $titleAfterSubmit = Wait-FileUploadTitle $browser.Id 'Upload Submitted sample-upload.txt' 40 + $serverSawUpload = Wait-FileUploadLogNeedle $serverErr 'UPLOAD files=1' 40 200 + if ((-not $titleAfterSubmit) -or (-not $serverSawUpload)) { + throw 'multipart upload did not complete with the selected file' + } + $submittedWorked = $true + + $log = if (Test-Path $serverErr) { Get-Content $serverErr -Raw } else { '' } + if ($log -notmatch 'sample-upload\.txt:38:primary upload payload from sample one') { + throw 'upload server did not receive the expected file payload' + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_after_submit = $titleAfterSubmit + selected_worked = $selectedWorked + submitted_worked = $submittedWorked + server_saw_upload = $serverSawUpload + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-target-attachment-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-target-attachment-probe.ps1 new file mode 100644 index 000000000..d7796ae1f --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-target-attachment-probe.ps1 @@ -0,0 +1,102 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-target-attachment' +$port = 8168 +$browserOut = Join-Path $root 'upload-target-attachment.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-target-attachment.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-target-attachment.server.stdout.txt' +$serverErr = Join-Path $root 'upload-target-attachment.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$samplePath = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$server = $null +$browser = $null +$ready = $false +$selectedWorked = $false +$downloadWorked = $false +$downloadsPageWorked = $false +$originPreserved = $false +$serverSawUpload = $false +$failure = $null +$titleBefore = $null +$titleDownloads = $null +$titleOrigin = $null + +try { + $profile = Reset-FileUploadProfile $profileRoot + $downloadPath = Join-Path $profile.DownloadsDir 'uploaded-sample-upload.txt' + + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload target attachment server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload-target-attachment.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload target attachment browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Target Attachment Smoke' 40 + if (-not $titleBefore) { throw 'file upload target attachment page did not load' } + + Invoke-FileUploadChoosePath $hwnd $browser.Id $samplePath + $selectedWorked = $true + + Invoke-FileUploadSubmit $hwnd + $serverSawUpload = Wait-FileUploadLogNeedle $serverErr 'UPLOAD_TARGET_ATTACHMENT files=1' 40 200 + if (-not $serverSawUpload) { throw 'target attachment upload server did not receive the selected file' } + if (-not (Wait-FileUploadFileExists $downloadPath 40 200)) { + throw 'target attachment upload did not create the expected downloaded file' + } + $downloadWorked = $true + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlJ + $titleDownloads = Wait-FileUploadTitle $browser.Id 'Downloads' 20 + if (-not $titleDownloads) { + throw 'downloads page did not open after target attachment upload' + } + $downloadsPageWorked = $true + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlDigit 1 + $titleOrigin = Wait-FileUploadTitle $browser.Id 'File Upload Target Attachment Smoke' 20 + if (-not $titleOrigin) { + throw 'source upload tab was not preserved after target attachment upload' + } + $originPreserved = $true +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_downloads = $titleDownloads + title_origin = $titleOrigin + selected_worked = $selectedWorked + download_worked = $downloadWorked + downloads_page_worked = $downloadsPageWorked + origin_preserved = $originPreserved + server_saw_upload = $serverSawUpload + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/chrome-file-upload-target-probe.ps1 b/tmp-browser-smoke/file-upload/chrome-file-upload-target-probe.ps1 new file mode 100644 index 000000000..42570c824 --- /dev/null +++ b/tmp-browser-smoke/file-upload/chrome-file-upload-target-probe.ps1 @@ -0,0 +1,89 @@ +$ErrorActionPreference = 'Stop' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$root = Join-Path $repo 'tmp-browser-smoke\file-upload' +$profileRoot = Join-Path $root 'profile-upload-target' +$port = 8166 +$browserOut = Join-Path $root 'upload-target.browser.stdout.txt' +$browserErr = Join-Path $root 'upload-target.browser.stderr.txt' +$serverOut = Join-Path $root 'upload-target.server.stdout.txt' +$serverErr = Join-Path $root 'upload-target.server.stderr.txt' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +. "$PSScriptRoot\FileUploadProbeCommon.ps1" + +$samplePath = (Resolve-Path (Join-Path $root 'sample-upload.txt')).Path +$server = $null +$browser = $null +$ready = $false +$selectedWorked = $false +$targetWorked = $false +$originPreserved = $false +$failure = $null +$titleBefore = $null +$titleTarget = $null +$titleOrigin = $null +$serverSawUpload = $false + +try { + Reset-FileUploadProfile $profileRoot | Out-Null + $server = Start-FileUploadServer $port $serverOut $serverErr + $ready = Wait-FileUploadServer $port + if (-not $ready) { throw 'file upload target server did not become ready' } + + $browser = Start-FileUploadBrowser "http://127.0.0.1:$port/upload-target.html" $browserOut $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw 'file upload target browser window handle not found' } + + $titleBefore = Wait-FileUploadTitle $browser.Id 'File Upload Target Smoke' 40 + if (-not $titleBefore) { throw 'file upload target page did not load' } + + Invoke-FileUploadChoosePath $hwnd $browser.Id $samplePath + $selectedWorked = $true + + Invoke-FileUploadSubmit $hwnd + $titleTarget = Wait-FileUploadTitle $browser.Id 'Upload Target Submitted sample-upload.txt' 40 + $serverSawUpload = Wait-FileUploadLogNeedle $serverErr 'UPLOAD_TARGET files=1' 40 200 + if ((-not $titleTarget) -or (-not $serverSawUpload)) { + throw 'named target upload did not open the expected result tab' + } + $targetWorked = $true + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlDigit 1 + $titleOrigin = Wait-FileUploadTitle $browser.Id 'File Upload Target Smoke' 20 + if (-not $titleOrigin) { + throw 'original upload tab was not preserved after target upload' + } + $originPreserved = $true +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + title_before = $titleBefore + title_target = $titleTarget + title_origin = $titleOrigin + selected_worked = $selectedWorked + target_worked = $targetWorked + origin_preserved = $originPreserved + server_saw_upload = $serverSawUpload + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/file-upload/sample-upload-second.txt b/tmp-browser-smoke/file-upload/sample-upload-second.txt new file mode 100644 index 000000000..0e7a0f543 --- /dev/null +++ b/tmp-browser-smoke/file-upload/sample-upload-second.txt @@ -0,0 +1 @@ +replacement upload payload from sample two \ No newline at end of file diff --git a/tmp-browser-smoke/file-upload/sample-upload.txt b/tmp-browser-smoke/file-upload/sample-upload.txt new file mode 100644 index 000000000..430a04937 --- /dev/null +++ b/tmp-browser-smoke/file-upload/sample-upload.txt @@ -0,0 +1 @@ +primary upload payload from sample one \ No newline at end of file diff --git a/tmp-browser-smoke/file-upload/upload-attachment.html b/tmp-browser-smoke/file-upload/upload-attachment.html new file mode 100644 index 000000000..1baec419b --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload-attachment.html @@ -0,0 +1,15 @@ + + + + + File Upload Attachment Smoke - Lightpanda Browser + + +

File Upload Attachment Smoke

+

Select a text file, then submit to a same-tab attachment response.

+
+ + +
+ + diff --git a/tmp-browser-smoke/file-upload/upload-multiple.html b/tmp-browser-smoke/file-upload/upload-multiple.html new file mode 100644 index 000000000..bb6e1b14c --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload-multiple.html @@ -0,0 +1,15 @@ + + + + + File Upload Multiple Smoke - Lightpanda Browser + + +

File Upload Multiple Smoke

+

Select multiple text files, then submit.

+
+ + +
+ + diff --git a/tmp-browser-smoke/file-upload/upload-target-attachment.html b/tmp-browser-smoke/file-upload/upload-target-attachment.html new file mode 100644 index 000000000..cce066f17 --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload-target-attachment.html @@ -0,0 +1,15 @@ + + + + + File Upload Target Attachment Smoke - Lightpanda Browser + + +

File Upload Target Attachment Smoke

+

Select a text file, then submit to a named popup target that returns an attachment.

+
+ + +
+ + diff --git a/tmp-browser-smoke/file-upload/upload-target.html b/tmp-browser-smoke/file-upload/upload-target.html new file mode 100644 index 000000000..e9ad047dd --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload-target.html @@ -0,0 +1,15 @@ + + + + + File Upload Target Smoke - Lightpanda Browser + + +

File Upload Target Smoke

+

Select a text file, then submit to the named popup tab.

+
+ + +
+ + diff --git a/tmp-browser-smoke/file-upload/upload.html b/tmp-browser-smoke/file-upload/upload.html new file mode 100644 index 000000000..14c506b25 --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload.html @@ -0,0 +1,15 @@ + + + + + File Upload Smoke - Lightpanda Browser + + +

File Upload Smoke

+

Select a text file, then submit.

+
+ + +
+ + diff --git a/tmp-browser-smoke/file-upload/upload_server.py b/tmp-browser-smoke/file-upload/upload_server.py new file mode 100644 index 000000000..6872c1f7c --- /dev/null +++ b/tmp-browser-smoke/file-upload/upload_server.py @@ -0,0 +1,192 @@ +import html +import os +import sys +from email import policy +from email.parser import BytesParser +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + + +ROOT = os.path.dirname(os.path.abspath(__file__)) + + +def html_page(title: str, body: str) -> bytes: + return f""" + + + + {html.escape(title)} + + +{body} + + +""".encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + @staticmethod + def _fixture_path(path: str) -> str | None: + if path in ("/", "/upload.html"): + return os.path.join(ROOT, "upload.html") + if path == "/upload-multiple.html": + return os.path.join(ROOT, "upload-multiple.html") + if path == "/upload-target.html": + return os.path.join(ROOT, "upload-target.html") + if path == "/upload-attachment.html": + return os.path.join(ROOT, "upload-attachment.html") + if path == "/upload-target-attachment.html": + return os.path.join(ROOT, "upload-target-attachment.html") + return None + + def _write_fixture(self, path: str) -> bool: + fixture = self._fixture_path(path) + if not fixture: + return False + with open(fixture, "rb") as handle: + data = handle.read() + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + return True + + def _write_html_response(self, status: int, title: str, body: str) -> None: + data = html_page(title, body) + self.send_response(status) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _write_attachment_response(self, filename: str, payload: bytes) -> None: + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Disposition", f'attachment; filename="{filename}"') + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, format, *args): + sys.stderr.write("HTTP " + (format % args) + "\n") + sys.stderr.flush() + + def do_GET(self): + if self.path == "/ping": + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + self.wfile.write(b"ok") + return + + if self._write_fixture(self.path): + return + + self.send_response(404) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + self.wfile.write(b"not found") + + def do_POST(self): + if self.path not in ( + "/upload", + "/upload-target", + "/upload-attachment", + "/upload-target-attachment", + ): + self.send_response(404) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + self.wfile.write(b"not found") + return + + note, uploads = parse_multipart_form(self.headers, self.rfile) + if not uploads: + sys.stderr.write("UPLOAD_EMPTY\n") + sys.stderr.flush() + self._write_html_response(400, "Upload Missing - Lightpanda Browser", "

Upload missing

") + return + + first_filename = os.path.basename(uploads[0][0]) + summaries = [] + for upload_name, payload in uploads: + filename = os.path.basename(upload_name) + preview = payload.decode("utf-8", "replace").replace("\r", "\\r").replace("\n", "\\n") + summaries.append(f"{filename}:{len(payload)}:{preview}") + summary = " | ".join(summaries) + if self.path == "/upload": + sys.stderr.write(f"UPLOAD files={len(uploads)} note={note} entries={summary}\n") + elif self.path == "/upload-target": + sys.stderr.write(f"UPLOAD_TARGET files={len(uploads)} note={note} entries={summary}\n") + elif self.path == "/upload-attachment": + sys.stderr.write(f"UPLOAD_ATTACHMENT files={len(uploads)} note={note} entries={summary}\n") + else: + sys.stderr.write(f"UPLOAD_TARGET_ATTACHMENT files={len(uploads)} note={note} entries={summary}\n") + sys.stderr.flush() + + if self.path == "/upload": + self._write_html_response( + 200, + f"Upload Submitted {first_filename} - Lightpanda Browser", + f"

Uploaded {html.escape(first_filename)}

{html.escape(note)}

", + ) + return + + if self.path == "/upload-target": + self._write_html_response( + 200, + f"Upload Target Submitted {first_filename} - Lightpanda Browser", + f"

Uploaded {html.escape(first_filename)} in target tab

{html.escape(note)}

", + ) + return + + attachment_name = f"uploaded-{first_filename}" + attachment_payload = uploads[0][1] if uploads[0][1] else first_filename.encode("utf-8") + self._write_attachment_response(attachment_name, attachment_payload) + + +def parse_multipart_form(headers, stream): + content_type = headers.get("Content-Type", "") + content_length = int(headers.get("Content-Length", "0") or "0") + raw = stream.read(content_length) + parser_input = ( + f"Content-Type: {content_type}\r\nMIME-Version: 1.0\r\n\r\n".encode("utf-8") + raw + ) + message = BytesParser(policy=policy.default).parsebytes(parser_input) + if not message.is_multipart(): + return "", [] + + note = "" + uploads = [] + + for part in message.iter_parts(): + disposition = part.get_content_disposition() + if disposition != "form-data": + continue + field_name = part.get_param("name", header="content-disposition") + filename = part.get_filename() + payload = part.get_payload(decode=True) or b"" + if filename: + uploads.append((filename, payload)) + elif field_name == "note": + note = payload.decode("utf-8", "replace") + + return note, uploads + + +def main() -> int: + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8162 + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + sys.stderr.write(f"UPLOAD_SERVER_READY {port}\n") + sys.stderr.flush() + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/find/chrome-find-probe.ps1 b/tmp-browser-smoke/find/chrome-find-probe.ps1 new file mode 100644 index 000000000..499bd243e --- /dev/null +++ b/tmp-browser-smoke/find/chrome-find-probe.ps1 @@ -0,0 +1,209 @@ +$ErrorActionPreference = "Stop" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\find" +$port = 8146 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$readyPng = Join-Path $root "find-ready.png" +$browserOut = Join-Path $root "find-browser.stdout.txt" +$browserErr = Join-Path $root "find-browser.stderr.txt" +$serverOut = Join-Path $root "find-server.stdout.txt" +$serverErr = Join-Path $root "find-server.stderr.txt" +Remove-Item $readyPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Get-ChildItem -Path $repo -Filter "lightpanda-screenshot-*.png" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Get-LatestAutoScreenshot([datetime]$AfterUtc) { + return Get-ChildItem -Path $repo -Filter "lightpanda-screenshot-*.png" -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTimeUtc -ge $AfterUtc.AddMilliseconds(-200) } | + Sort-Object LastWriteTimeUtc -Descending | + Select-Object -First 1 +} + +function Wait-AutoScreenshot([datetime]$AfterUtc) { + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $latest = Get-LatestAutoScreenshot $AfterUtc + if ($latest) { return $latest.FullName } + } + throw "find probe screenshot did not become ready" +} + +$server = $null +$browser = $null +$ready = $false +$readyPngWritten = $false +$hwnd = [IntPtr]::Zero +$firstPng = $null +$secondPng = $null +$thirdPng = $null +$firstBounds = $null +$secondBounds = $null +$thirdBounds = $null +$findNextPoint = $null +$findPreviousPoint = $null +$titleBefore = $null +$titleAfterFind = $null +$titleAfterNext = $null +$titleAfterPrevious = $null +$findWorked = $false +$nextWorked = $false +$previousWorked = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "find probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","360","--window_height","420","--screenshot_png",$readyPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $readyPng) -and ((Get-Item $readyPng).Length -gt 0)) { $readyPngWritten = $true; break } + } + if (-not $readyPngWritten) { throw "find probe ready screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "find probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 350 + $titleBefore = Get-SmokeWindowTitle $hwnd + + Send-SmokeCtrlF + Start-Sleep -Milliseconds 150 + Send-SmokeText "target" + Start-Sleep -Milliseconds 350 + $titleAfterFind = Get-SmokeWindowTitle $hwnd + + $captureStart = [DateTime]::UtcNow + Send-SmokeCtrlShiftP + $firstPng = Wait-AutoScreenshot $captureStart + + $bmp = [System.Drawing.Bitmap]::new($firstPng) + try { + $clientWidth = $bmp.Width + $firstBounds = Get-ColorBounds $bmp { param($c) $c.R -ge 250 -and $c.G -ge 235 -and $c.G -le 250 -and $c.B -ge 120 -and $c.B -le 180 } + } finally { + $bmp.Dispose() + } + if ($null -eq $firstBounds.min_y) { throw "find probe could not find initial highlight" } + $findWorked = $true + + $findNextX = [int]($clientWidth - 24) + $findPreviousX = [int]($clientWidth - 48) + $findButtonY = 15 + + Start-Sleep -Milliseconds 1200 + $findNextPoint = Invoke-SmokeClientClick $hwnd $findNextX $findButtonY + Start-Sleep -Milliseconds 350 + $titleAfterNext = Get-SmokeWindowTitle $hwnd + + $captureStart = [DateTime]::UtcNow + Send-SmokeCtrlShiftP + $secondPng = Wait-AutoScreenshot $captureStart + + $bmp = [System.Drawing.Bitmap]::new($secondPng) + try { + $secondBounds = Get-ColorBounds $bmp { param($c) $c.R -ge 250 -and $c.G -ge 235 -and $c.G -le 250 -and $c.B -ge 120 -and $c.B -le 180 } + } finally { + $bmp.Dispose() + } + if ($null -eq $secondBounds.min_y) { throw "find probe could not find next highlight" } + + $nextWorked = ($secondBounds.min_y -gt ($firstBounds.min_y + 20)) + if (-not $nextWorked) { throw "find probe highlight did not move after next button click" } + + Start-Sleep -Milliseconds 1200 + $findPreviousPoint = Invoke-SmokeClientClick $hwnd $findPreviousX $findButtonY + Start-Sleep -Milliseconds 350 + $titleAfterPrevious = Get-SmokeWindowTitle $hwnd + + $captureStart = [DateTime]::UtcNow + Send-SmokeCtrlShiftP + $thirdPng = Wait-AutoScreenshot $captureStart + + $bmp = [System.Drawing.Bitmap]::new($thirdPng) + try { + $thirdBounds = Get-ColorBounds $bmp { param($c) $c.R -ge 250 -and $c.G -ge 235 -and $c.G -le 250 -and $c.B -ge 120 -and $c.B -le 180 } + } finally { + $bmp.Dispose() + } + if ($null -eq $thirdBounds.min_y) { throw "find probe could not find previous highlight" } + + $previousWorked = ($thirdBounds.min_y -lt ($secondBounds.min_y - 20)) + if (-not $previousWorked) { throw "find probe highlight did not move back after previous button click" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + ready_screenshot = $readyPngWritten + ready_png = $readyPng + first_png = $firstPng + second_png = $secondPng + third_png = $thirdPng + title_before = $titleBefore + title_after_find = $titleAfterFind + title_after_next = $titleAfterNext + title_after_previous = $titleAfterPrevious + find_next_point = $findNextPoint + find_previous_point = $findPreviousPoint + first_bounds = $firstBounds + second_bounds = $secondBounds + third_bounds = $thirdBounds + find_worked = $findWorked + next_worked = $nextWorked + previous_worked = $previousWorked + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/find/index.html b/tmp-browser-smoke/find/index.html new file mode 100644 index 000000000..ad881b707 --- /dev/null +++ b/tmp-browser-smoke/find/index.html @@ -0,0 +1,12 @@ + + + + + Find Smoke + + +
intro target alpha
+
middle target beta
+
late target gamma
+ + diff --git a/tmp-browser-smoke/flow-layout/green.png b/tmp-browser-smoke/flow-layout/green.png new file mode 100644 index 000000000..1a5db7393 Binary files /dev/null and b/tmp-browser-smoke/flow-layout/green.png differ diff --git a/tmp-browser-smoke/flow-layout/index.html b/tmp-browser-smoke/flow-layout/index.html new file mode 100644 index 000000000..4bb68f381 --- /dev/null +++ b/tmp-browser-smoke/flow-layout/index.html @@ -0,0 +1,22 @@ + + + + + Flow Layout Smoke + + + +
+

Flow Layout Smoke

+

First image should sit above the second image with visible whitespace between them.

+ red block +

Second paragraph should appear between the two images.

+ green block +
+ + diff --git a/tmp-browser-smoke/flow-layout/probe.ps1 b/tmp-browser-smoke/flow-layout/probe.ps1 new file mode 100644 index 000000000..c392b0e2b --- /dev/null +++ b/tmp-browser-smoke/flow-layout/probe.ps1 @@ -0,0 +1,83 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\flow-layout" +$port = 8137 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "flow-layout.png" +$browserOut = Join-Path $root "browser.stdout.txt" +$browserErr = Join-Path $root "browser.stderr.txt" +$serverOut = Join-Path $root "server.stdout.txt" +$serverErr = Join-Path $root "server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +$server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost probe server did not become ready" } +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } +} +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $red = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90) { + if ($null -eq $red.min_x -or $x -lt $red.min_x) { $red.min_x = $x } + if ($null -eq $red.min_y -or $y -lt $red.min_y) { $red.min_y = $y } + if ($null -eq $red.max_x -or $x -gt $red.max_x) { $red.max_x = $x } + if ($null -eq $red.max_y -or $y -gt $red.max_y) { $red.max_y = $y } + $red.count++ + } + if ($c.G -ge 100 -and $c.R -le 120 -and $c.B -le 120) { + if ($null -eq $green.min_x -or $x -lt $green.min_x) { $green.min_x = $x } + if ($null -eq $green.min_y -or $y -lt $green.min_y) { $green.min_y = $y } + if ($null -eq $green.max_x -or $x -gt $green.max_x) { $green.max_x = $x } + if ($null -eq $green.max_y -or $y -gt $green.max_y) { $green.max_y = $y } + $green.count++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red = $red + green = $green + vertical_gap = if ($null -ne $red.max_y -and $null -ne $green.min_y) { $green.min_y - $red.max_y - 1 } else { $null } + green_below_red = if ($null -ne $red.max_y -and $null -ne $green.min_y) { $green.min_y -gt $red.max_y } else { $false } + } + } finally { + $bmp.Dispose() + } +} +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/flow-layout/red.png b/tmp-browser-smoke/flow-layout/red.png new file mode 100644 index 000000000..1bc71ca05 Binary files /dev/null and b/tmp-browser-smoke/flow-layout/red.png differ diff --git a/tmp-browser-smoke/font-render/button-layout.html b/tmp-browser-smoke/font-render/button-layout.html new file mode 100644 index 000000000..88f6bacaf --- /dev/null +++ b/tmp-browser-smoke/font-render/button-layout.html @@ -0,0 +1,11 @@ + + + +
+ +
+
+ +
+ + diff --git a/tmp-browser-smoke/font-render/chrome-font-button-layout-probe.ps1 b/tmp-browser-smoke/font-render/chrome-font-button-layout-probe.ps1 new file mode 100644 index 000000000..626555879 --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-font-button-layout-probe.ps1 @@ -0,0 +1,138 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$port = 8166 +$serverOut = Join-Path $root "font-button-layout.server.stdout.txt" +$serverErr = Join-Path $root "font-button-layout.server.stderr.txt" + +Remove-Item $serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Measure-ColorBounds($Path, [scriptblock]$Predicate) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + if ($maxX -lt 0 -or $maxY -lt 0) { throw "colored region not found in $Path" } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } + finally { + $bmp.Dispose() + } +} + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/button-layout.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "font button layout server did not become ready" } + + $profileRoot = Join-Path $root "profile-font-button-layout" + $appDataRoot = Join-Path $profileRoot "lightpanda" + $outPng = Join-Path $root "font-button-layout.png" + $browserOut = Join-Path $root "font-button-layout.browser.stdout.txt" + $browserErr = Join-Path $root "font-button-layout.browser.stderr.txt" + Remove-Item $outPng,$browserOut,$browserErr -Force -ErrorAction SilentlyContinue + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/button-layout.html","--window_width","980","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + $readyPng = $false + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $readyPng = $true + break + } + } + if (-not $readyPng) { throw "font button layout screenshot did not become ready" } + + $red = Measure-ColorBounds $outPng { param($c) $c.R -ge 150 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Measure-ColorBounds $outPng { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 90 } + $widthDelta = [math]::Abs([int]$red.width - [int]$blue.width) + $layoutWorked = $widthDelta -ge 10 + if (-not $layoutWorked) { + throw "font button layout probe did not observe a sufficient width delta" + } + + [ordered]@{ + ready = $true + red = $red + blue = $blue + width_delta = $widthDelta + layout_worked = $layoutWorked + } | ConvertTo-Json -Depth 5 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/font-render/chrome-font-render-probe.ps1 b/tmp-browser-smoke/font-render/chrome-font-render-probe.ps1 new file mode 100644 index 000000000..3c437880a --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-font-render-probe.ps1 @@ -0,0 +1,183 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$profileRoot = Join-Path $root "profile-font-render" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8163 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$outPng = Join-Path $root "font-render.png" +$browserOut = Join-Path $root "font-render.browser.stdout.txt" +$browserErr = Join-Path $root "font-render.browser.stderr.txt" +$serverOut = Join-Path $root "font-render.server.stdout.txt" +$serverErr = Join-Path $root "font-render.server.stderr.txt" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "font render server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","520","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} +if (-not $pngReady) { throw "font render screenshot did not become ready" } + +$redBounds = $null +$blueBounds = $null +$widthDelta = 0 +$fontWorked = $false +$browserCommand = "" +$serverCommand = "" +$browserGone = $false +$serverGone = $false + +try { + Add-Type -AssemblyName System.Drawing + + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redMinX = $bmp.Width + $redMinY = $bmp.Height + $redMaxX = -1 + $redMaxY = -1 + $blueMinX = $bmp.Width + $blueMinY = $bmp.Height + $blueMaxX = -1 + $blueMaxY = -1 + + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 120 -and $c.G -le 100 -and $c.B -le 100) { + if ($x -lt $redMinX) { $redMinX = $x } + if ($y -lt $redMinY) { $redMinY = $y } + if ($x -gt $redMaxX) { $redMaxX = $x } + if ($y -gt $redMaxY) { $redMaxY = $y } + } + if ($c.B -ge 120 -and $c.G -le 100 -and $c.R -le 100) { + if ($x -lt $blueMinX) { $blueMinX = $x } + if ($y -lt $blueMinY) { $blueMinY = $y } + if ($x -gt $blueMaxX) { $blueMaxX = $x } + if ($y -gt $blueMaxY) { $blueMaxY = $y } + } + } + } + } finally { + $bmp.Dispose() + } + + if ($redMaxX -ge 0 -and $redMaxY -ge 0) { + $redBounds = [ordered]@{ + left = $redMinX + top = $redMinY + right = $redMaxX + bottom = $redMaxY + width = $redMaxX - $redMinX + 1 + height = $redMaxY - $redMinY + 1 + } + } + if ($blueMaxX -ge 0 -and $blueMaxY -ge 0) { + $blueBounds = [ordered]@{ + left = $blueMinX + top = $blueMinY + right = $blueMaxX + bottom = $blueMaxY + width = $blueMaxX - $blueMinX + 1 + height = $blueMaxY - $blueMinY + 1 + } + } + break + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } + + $widthDelta = if ($redBounds -and $blueBounds) { [math]::Abs([int]$redBounds.width - [int]$blueBounds.width) } else { 0 } + $fontWorked = ($redBounds -ne $null) -and ($blueBounds -ne $null) -and $widthDelta -ge 40 + if (-not $fontWorked) { + throw "font render probe did not observe materially different glyph widths" + } +} +finally { + $browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($browserMeta) { + $browserCommand = [string]$browserMeta.CommandLine + if ($browserCommand -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $browser.Id -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) { throw } + } + } + } + + $serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($serverMeta) { + $serverCommand = [string]$serverMeta.CommandLine + if ($serverCommand -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $server.Id -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) { throw } + } + } + } + + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + $browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + $serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) +} + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + red_bounds = $redBounds + blue_bounds = $blueBounds + width_delta = $widthDelta + font_worked = $fontWorked + browser_command = $browserCommand + server_command = $serverCommand + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/font-render/chrome-private-font-fallback-probe.ps1 b/tmp-browser-smoke/font-render/chrome-private-font-fallback-probe.ps1 new file mode 100644 index 000000000..67f62e458 --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-private-font-fallback-probe.ps1 @@ -0,0 +1,159 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$port = 8163 +$serverOut = Join-Path $root "private-font-fallback.server.stdout.txt" +$serverErr = Join-Path $root "private-font-fallback.server.stderr.txt" + +Remove-Item $serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Measure-RedWidth($Path) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 120 -and $c.G -le 100 -and $c.B -le 100) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + } finally { + $bmp.Dispose() + } + if ($maxX -lt 0 -or $maxY -lt 0) { throw "red text not found in $Path" } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +function Run-BrowserCapture([string]$Name, [string]$Url) { + $profileRoot = Join-Path $root ("profile-private-font-fallback-" + $Name) + $appDataRoot = Join-Path $profileRoot "lightpanda" + $outPng = Join-Path $root ("private-font-fallback-" + $Name + ".png") + $browserOut = Join-Path $root ("private-font-fallback-" + $Name + ".browser.stdout.txt") + $browserErr = Join-Path $root ("private-font-fallback-" + $Name + ".browser.stderr.txt") + + Remove-Item $outPng,$browserOut,$browserErr -Force -ErrorAction SilentlyContinue + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$Url,"--window_width","980","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $result = $null + try { + $pngReady = $false + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } + } + if (-not $pngReady) { throw "screenshot for $Name did not become ready" } + $bounds = Measure-RedWidth $outPng + $result = [ordered]@{ + pid = $browser.Id + screenshot = $outPng + screenshot_length = (Get-Item $outPng).Length + bounds = $bounds + browser_command = Get-ProcessCommandLine $browser.Id + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + if ($result) { + $result["browser_gone"] = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + } + } + return $result +} + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/private-fallback.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "private fallback font render server did not become ready" } + + $fallback = Run-BrowserCapture "fallback" "http://127.0.0.1:$port/private-fallback.html" + $missing = Run-BrowserCapture "missing" "http://127.0.0.1:$port/private-missing.html" + $widthDelta = [math]::Abs([int]$fallback.bounds.width - [int]$missing.bounds.width) + $fontWorked = $widthDelta -ge 20 + if (-not $fontWorked) { + throw "private fallback font render probe did not observe a sufficient width delta" + } + + [ordered]@{ + ready = $ready + fallback = $fallback + missing = $missing + width_delta = $widthDelta + font_worked = $fontWorked + } | ConvertTo-Json -Depth 6 +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/font-render/chrome-private-font-render-probe.ps1 b/tmp-browser-smoke/font-render/chrome-private-font-render-probe.ps1 new file mode 100644 index 000000000..bb4a405b8 --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-private-font-render-probe.ps1 @@ -0,0 +1,164 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$port = 8163 +$serverOut = Join-Path $root "private-font.server.stdout.txt" +$serverErr = Join-Path $root "private-font.server.stderr.txt" + +Remove-Item $serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Measure-RedWidth($Path) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 120 -and $c.G -le 100 -and $c.B -le 100) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + } finally { + $bmp.Dispose() + } + if ($maxX -lt 0 -or $maxY -lt 0) { throw "red text not found in $Path" } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +function Run-BrowserCapture([string]$Name, [string]$Url) { + $profileRoot = Join-Path $root ("profile-private-font-" + $Name) + $appDataRoot = Join-Path $profileRoot "lightpanda" + $outPng = Join-Path $root ("private-font-" + $Name + ".png") + $browserOut = Join-Path $root ("private-font-" + $Name + ".browser.stdout.txt") + $browserErr = Join-Path $root ("private-font-" + $Name + ".browser.stderr.txt") + + Remove-Item $outPng,$browserOut,$browserErr -Force -ErrorAction SilentlyContinue + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$Url,"--window_width","980","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $result = $null + try { + $pngReady = $false + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } + } + if (-not $pngReady) { throw "screenshot for $Name did not become ready" } + $bounds = Measure-RedWidth $outPng + $result = [ordered]@{ + pid = $browser.Id + screenshot = $outPng + screenshot_length = (Get-Item $outPng).Length + bounds = $bounds + browser_command = Get-ProcessCommandLine $browser.Id + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + if ($result) { + $result["browser_gone"] = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + } + } + return $result +} + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$serverCommand = "" +$serverGone = $false + +try { + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/private-loaded.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "private font render server did not become ready" } + + $loaded = Run-BrowserCapture "loaded" "http://127.0.0.1:$port/private-loaded.html" + $missing = Run-BrowserCapture "missing" "http://127.0.0.1:$port/private-missing.html" + $widthDelta = [math]::Abs([int]$loaded.bounds.width - [int]$missing.bounds.width) + $fontWorked = $widthDelta -ge 20 + if (-not $fontWorked) { + throw "private font render probe did not observe a sufficient width delta" + } + + [ordered]@{ + ready = $ready + loaded = $loaded + missing = $missing + width_delta = $widthDelta + font_worked = $fontWorked + } | ConvertTo-Json -Depth 6 +} +finally { + $serverCommand = Get-ProcessCommandLine $server.Id + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + $serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) +} diff --git a/tmp-browser-smoke/font-render/chrome-private-font-woff-probe.ps1 b/tmp-browser-smoke/font-render/chrome-private-font-woff-probe.ps1 new file mode 100644 index 000000000..c6f6c77b2 --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-private-font-woff-probe.ps1 @@ -0,0 +1,160 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$port = 8165 +$serverOut = Join-Path $root "private-font-woff.server.stdout.txt" +$serverErr = Join-Path $root "private-font-woff.server.stderr.txt" + +Remove-Item $serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Measure-RedWidth($Path) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 120 -and $c.G -le 100 -and $c.B -le 100) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + } finally { + $bmp.Dispose() + } + if ($maxX -lt 0 -or $maxY -lt 0) { throw "red text not found in $Path" } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +function Run-BrowserCapture([string]$Name, [string]$Url) { + $profileRoot = Join-Path $root ("profile-private-font-woff-" + $Name) + $appDataRoot = Join-Path $profileRoot "lightpanda" + $outPng = Join-Path $root ("private-font-woff-" + $Name + ".png") + $browserOut = Join-Path $root ("private-font-woff-" + $Name + ".browser.stdout.txt") + $browserErr = Join-Path $root ("private-font-woff-" + $Name + ".browser.stderr.txt") + + Remove-Item $outPng,$browserOut,$browserErr -Force -ErrorAction SilentlyContinue + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$Url,"--window_width","980","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $result = $null + try { + $pngReady = $false + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } + } + if (-not $pngReady) { throw "screenshot for $Name did not become ready" } + $bounds = Measure-RedWidth $outPng + $result = [ordered]@{ + pid = $browser.Id + screenshot = $outPng + screenshot_length = (Get-Item $outPng).Length + bounds = $bounds + browser_command = Get-ProcessCommandLine $browser.Id + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + if ($result) { + $result["browser_gone"] = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + } + } + return $result +} + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/private-woff-loaded.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "woff private font render server did not become ready" } + + $loaded = Run-BrowserCapture "loaded" "http://127.0.0.1:$port/private-woff-loaded.html" + $missing = Run-BrowserCapture "missing" "http://127.0.0.1:$port/private-woff-missing.html" + $widthDelta = [math]::Abs([int]$loaded.bounds.width - [int]$missing.bounds.width) + $fontWorked = $widthDelta -ge 10 + if (-not $fontWorked) { + throw "private woff font render probe did not observe a sufficient width delta" + } + + [ordered]@{ + ready = $ready + loaded = $loaded + missing = $missing + width_delta = $widthDelta + font_worked = $fontWorked + } | ConvertTo-Json -Depth 6 +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/font-render/chrome-private-font-woff2-probe.ps1 b/tmp-browser-smoke/font-render/chrome-private-font-woff2-probe.ps1 new file mode 100644 index 000000000..454d2d220 --- /dev/null +++ b/tmp-browser-smoke/font-render/chrome-private-font-woff2-probe.ps1 @@ -0,0 +1,160 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-render" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_render_server.py" +$port = 8164 +$serverOut = Join-Path $root "private-font-woff2.server.stdout.txt" +$serverErr = Join-Path $root "private-font-woff2.server.stderr.txt" + +Remove-Item $serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Measure-RedWidth($Path) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 120 -and $c.G -le 100 -and $c.B -le 100) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + } finally { + $bmp.Dispose() + } + if ($maxX -lt 0 -or $maxY -lt 0) { throw "red text not found in $Path" } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +function Run-BrowserCapture([string]$Name, [string]$Url) { + $profileRoot = Join-Path $root ("profile-private-font-woff2-" + $Name) + $appDataRoot = Join-Path $profileRoot "lightpanda" + $outPng = Join-Path $root ("private-font-woff2-" + $Name + ".png") + $browserOut = Join-Path $root ("private-font-woff2-" + $Name + ".browser.stdout.txt") + $browserErr = Join-Path $root ("private-font-woff2-" + $Name + ".browser.stderr.txt") + + Remove-Item $outPng,$browserOut,$browserErr -Force -ErrorAction SilentlyContinue + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$Url,"--window_width","980","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $result = $null + try { + $pngReady = $false + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } + } + if (-not $pngReady) { throw "screenshot for $Name did not become ready" } + $bounds = Measure-RedWidth $outPng + $result = [ordered]@{ + pid = $browser.Id + screenshot = $outPng + screenshot_length = (Get-Item $outPng).Length + bounds = $bounds + browser_command = Get-ProcessCommandLine $browser.Id + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + if ($result) { + $result["browser_gone"] = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + } + } + return $result +} + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/private-woff2-loaded.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "woff2 private font render server did not become ready" } + + $loaded = Run-BrowserCapture "loaded" "http://127.0.0.1:$port/private-woff2-loaded.html" + $missing = Run-BrowserCapture "missing" "http://127.0.0.1:$port/private-woff2-missing.html" + $widthDelta = [math]::Abs([int]$loaded.bounds.width - [int]$missing.bounds.width) + $fontWorked = $widthDelta -ge 10 + if (-not $fontWorked) { + throw "private woff2 font render probe did not observe a sufficient width delta" + } + + [ordered]@{ + ready = $ready + loaded = $loaded + missing = $missing + width_delta = $widthDelta + font_worked = $fontWorked + } | ConvertTo-Json -Depth 6 +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/font-render/font_render_server.py b/tmp-browser-smoke/font-render/font_render_server.py new file mode 100644 index 000000000..9be909c20 --- /dev/null +++ b/tmp-browser-smoke/font-render/font_render_server.py @@ -0,0 +1,50 @@ +from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parent +REPO_FONT = ROOT.parent.parent / "src" / "browser" / "tests" / "css" / "private_font_test.ttf" +REPO_FONT_WOFF = ROOT.parent.parent / "src" / "browser" / "tests" / "css" / "font_face_test.woff" +REPO_FONT_WOFF2 = ROOT.parent.parent / "src" / "browser" / "tests" / "css" / "font_face_test.woff2" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8163 + + +class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(ROOT), **kwargs) + + def do_GET(self): + if self.path == "/private_font_test.ttf": + data = REPO_FONT.read_bytes() + self.send_response(200) + self.send_header("Content-Type", "font/ttf") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + return + if self.path == "/font_face_test.woff": + data = REPO_FONT_WOFF.read_bytes() + self.send_response(200) + self.send_header("Content-Type", "font/woff") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + return + if self.path == "/font_face_test.woff2": + data = REPO_FONT_WOFF2.read_bytes() + self.send_response(200) + self.send_header("Content-Type", "font/woff2") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + return + super().do_GET() + + +if __name__ == "__main__": + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + try: + server.serve_forever() + finally: + server.server_close() diff --git a/tmp-browser-smoke/font-render/index.html b/tmp-browser-smoke/font-render/index.html new file mode 100644 index 000000000..7d1d614f2 --- /dev/null +++ b/tmp-browser-smoke/font-render/index.html @@ -0,0 +1,11 @@ + + + +
+ iiiiiiiiiiiiiiii +
+
+ iiiiiiiiiiiiiiii +
+ + diff --git a/tmp-browser-smoke/font-render/private-fallback.html b/tmp-browser-smoke/font-render/private-fallback.html new file mode 100644 index 000000000..e00d7cffc --- /dev/null +++ b/tmp-browser-smoke/font-render/private-fallback.html @@ -0,0 +1,28 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/tmp-browser-smoke/font-render/private-loaded.html b/tmp-browser-smoke/font-render/private-loaded.html new file mode 100644 index 000000000..a832ce63e --- /dev/null +++ b/tmp-browser-smoke/font-render/private-loaded.html @@ -0,0 +1,26 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/tmp-browser-smoke/font-render/private-missing.html b/tmp-browser-smoke/font-render/private-missing.html new file mode 100644 index 000000000..52228fc89 --- /dev/null +++ b/tmp-browser-smoke/font-render/private-missing.html @@ -0,0 +1,26 @@ + + + + + + + PrivateFontWidthProof + + diff --git a/tmp-browser-smoke/font-render/private-woff-loaded.html b/tmp-browser-smoke/font-render/private-woff-loaded.html new file mode 100644 index 000000000..4f2df0d42 --- /dev/null +++ b/tmp-browser-smoke/font-render/private-woff-loaded.html @@ -0,0 +1,26 @@ + + + + + + + PrivateWoffWidthProof + + diff --git a/tmp-browser-smoke/font-render/private-woff-missing.html b/tmp-browser-smoke/font-render/private-woff-missing.html new file mode 100644 index 000000000..fae88f2b9 --- /dev/null +++ b/tmp-browser-smoke/font-render/private-woff-missing.html @@ -0,0 +1,26 @@ + + + + + + + PrivateWoffWidthProof + + diff --git a/tmp-browser-smoke/font-render/private-woff2-loaded.html b/tmp-browser-smoke/font-render/private-woff2-loaded.html new file mode 100644 index 000000000..6cd6f122f --- /dev/null +++ b/tmp-browser-smoke/font-render/private-woff2-loaded.html @@ -0,0 +1,26 @@ + + + + + + + PrivateWoff2WidthProof + + diff --git a/tmp-browser-smoke/font-render/private-woff2-missing.html b/tmp-browser-smoke/font-render/private-woff2-missing.html new file mode 100644 index 000000000..4f6309b61 --- /dev/null +++ b/tmp-browser-smoke/font-render/private-woff2-missing.html @@ -0,0 +1,26 @@ + + + + + + + PrivateWoff2WidthProof + + diff --git a/tmp-browser-smoke/font-smoke/chrome-font-anonymous-probe.ps1 b/tmp-browser-smoke/font-smoke/chrome-font-anonymous-probe.ps1 new file mode 100644 index 000000000..b76e2c087 --- /dev/null +++ b/tmp-browser-smoke/font-smoke/chrome-font-anonymous-probe.ps1 @@ -0,0 +1,92 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-smoke" +$profileRoot = Join-Path $root "profile-font-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8162 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_server.py" +$browserOut = Join-Path $root "font-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "font-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "font-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "font-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "font.requests.jsonl" +$pageUrl = "http://127.0.0.1:$port/auth-font-anonymous-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-font-anonymous-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost font anonymous server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$fontEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $fontEntries = @($entries | Where-Object { $_.path -eq "/private-font-anonymous.woff2" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded-anon" }) + if ($fontEntries.Count -gt 0) { $fontEntry = $fontEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($fontEntry -and $loadedEntry) { break } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + font_allowed = if ($fontEntry) { [bool]$fontEntry.allowed } else { $false } + font_user_agent = if ($fontEntry) { [string]$fontEntry.user_agent } else { "" } + font_cookie = if ($fontEntry) { [string]$fontEntry.cookie } else { "" } + font_referer = if ($fontEntry) { [string]$fontEntry.referer } else { "" } + font_authorization = if ($fontEntry) { [string]$fontEntry.authorization } else { "" } + font_accept = if ($fontEntry) { [string]$fontEntry.accept } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + loaded_size = if ($loadedEntry) { [string]$loadedEntry.size } else { "" } + loaded_status = if ($loadedEntry) { [string]$loadedEntry.status } else { "" } + loaded_check = if ($loadedEntry) { [string]$loadedEntry.check } else { "" } + loaded_count = if ($loadedEntry) { [string]$loadedEntry.loadCount } else { "" } + loaded_family = if ($loadedEntry) { [string]$loadedEntry.family } else { "" } + loaded_sheet = if ($loadedEntry) { [string]$loadedEntry.sheet } else { "" } + loaded_rules = if ($loadedEntry) { [string]$loadedEntry.rules } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/font-smoke/chrome-font-auth-probe.ps1 b/tmp-browser-smoke/font-smoke/chrome-font-auth-probe.ps1 new file mode 100644 index 000000000..6683b7602 --- /dev/null +++ b/tmp-browser-smoke/font-smoke/chrome-font-auth-probe.ps1 @@ -0,0 +1,92 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\font-smoke" +$profileRoot = Join-Path $root "profile-font-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8162 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "font_server.py" +$browserOut = Join-Path $root "font-auth.browser.stdout.txt" +$browserErr = Join-Path $root "font-auth.browser.stderr.txt" +$serverOut = Join-Path $root "font-auth.server.stdout.txt" +$serverErr = Join-Path $root "font-auth.server.stderr.txt" +$requestLog = Join-Path $root "font.requests.jsonl" +$pageUrl = "http://fontuser:p%40ss@127.0.0.1:$port/auth-font-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-font-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost font auth server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$fontEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $fontEntries = @($entries | Where-Object { $_.path -eq "/private-font.woff2" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded" }) + if ($fontEntries.Count -gt 0) { $fontEntry = $fontEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($fontEntry -and $loadedEntry) { break } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + font_allowed = if ($fontEntry) { [bool]$fontEntry.allowed } else { $false } + font_user_agent = if ($fontEntry) { [string]$fontEntry.user_agent } else { "" } + font_cookie = if ($fontEntry) { [string]$fontEntry.cookie } else { "" } + font_referer = if ($fontEntry) { [string]$fontEntry.referer } else { "" } + font_authorization = if ($fontEntry) { [string]$fontEntry.authorization } else { "" } + font_accept = if ($fontEntry) { [string]$fontEntry.accept } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + loaded_size = if ($loadedEntry) { [string]$loadedEntry.size } else { "" } + loaded_status = if ($loadedEntry) { [string]$loadedEntry.status } else { "" } + loaded_check = if ($loadedEntry) { [string]$loadedEntry.check } else { "" } + loaded_count = if ($loadedEntry) { [string]$loadedEntry.loadCount } else { "" } + loaded_family = if ($loadedEntry) { [string]$loadedEntry.family } else { "" } + loaded_sheet = if ($loadedEntry) { [string]$loadedEntry.sheet } else { "" } + loaded_rules = if ($loadedEntry) { [string]$loadedEntry.rules } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/font-smoke/font_server.py b/tmp-browser-smoke/font-smoke/font_server.py new file mode 100644 index 000000000..bc40b712e --- /dev/null +++ b/tmp-browser-smoke/font-smoke/font_server.py @@ -0,0 +1,306 @@ +import base64 +import json +import sys +from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from urllib.parse import parse_qs, urlparse + + +ROOT = Path(__file__).resolve().parent +REQUEST_LOG = ROOT / "font.requests.jsonl" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8162 +AUTH_HEADER = "Basic " + base64.b64encode(b"fontuser:p@ss").decode("ascii") +FONT_BYTES = b"dummy-font-bytes-for-shared-runtime-font-tests" + + +def append_log(entry: dict) -> None: + with REQUEST_LOG.open("a", encoding="utf-8") as fh: + fh.write(json.dumps(entry) + "\n") + + +def page_body(title: str, link_source: str, crossorigin: str | None, beacon_path: str) -> bytes: + crossorigin_script = "" + if crossorigin is not None: + crossorigin_script = f"link.crossOrigin = {crossorigin!r};" + + body = f""" + +{title} + + + + +""" + return body.encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + if self.path == "/auth-font-page.html": + body = page_body("Font Pending", "/private-font.css", None, "/loaded") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpfont=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-font-anonymous-page.html": + body = page_body( + "Font Anonymous Pending", + f"http://fontuser:p%40ss@127.0.0.1:{PORT}/private-font-anonymous.css", + "anonymous", + "/loaded-anon", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpfontanon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-font.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-font-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpfont=ok" in cookie + and referer == expected_referer + and authorization == AUTH_HEADER + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body { background: rgb(255, 0, 0); }" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = ( + '@font-face { font-family: "Probe Font"; src: url("/private-font.woff2"); } ' + 'body { font-family: "Probe Font", sans-serif; }' + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-font-anonymous.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-font-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body { background: rgb(255, 0, 0); }" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = ( + f'@font-face {{ font-family: "Probe Font"; src: url("http://fontuser:p%40ss@127.0.0.1:{PORT}/private-font-anonymous.woff2"); }} ' + 'body { font-family: "Probe Font", sans-serif; }' + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-font.woff2": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/private-font.css" + allowed = ( + "Lightpanda/" in ua + and "lpfont=ok" in cookie + and referer == expected_referer + and authorization == AUTH_HEADER + and "font/woff2" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_response(200) + self.send_header("Content-Type", "font/woff2") + self.send_header("Content-Length", str(len(FONT_BYTES))) + self.end_headers() + self.wfile.write(FONT_BYTES) + return + + if self.path == "/private-font-anonymous.woff2": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/private-font-anonymous.css" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "font/woff2" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_response(200) + self.send_header("Content-Type", "font/woff2") + self.send_header("Content-Length", str(len(FONT_BYTES))) + self.end_headers() + self.wfile.write(FONT_BYTES) + return + + if self.path.startswith("/loaded"): + parsed = urlparse(self.path) + params = parse_qs(parsed.query) + entry = { + "path": parsed.path, + "size": params.get("size", [""])[0], + "status": params.get("status", [""])[0], + "check": params.get("check", [""])[0], + "loadCount": params.get("loadCount", [""])[0], + "family": params.get("family", [""])[0], + "sheet": params.get("sheet", [""])[0], + "rules": params.get("rules", [""])[0], + } + entry["allowed"] = ( + entry["size"] == "1" + and entry["status"] == "loaded" + and entry["check"] == "1" + and entry["loadCount"] == "1" + and "Probe Font" in entry["family"] + and entry["sheet"] == "1" + and entry["rules"] == "2" + ) + append_log(entry) + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"not found" + self.send_response(404) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format: str, *args) -> None: + return + + +def main() -> int: + REQUEST_LOG.write_text("", encoding="utf-8") + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/form-controls/enter-submit-probe.ps1 b/tmp-browser-smoke/form-controls/enter-submit-probe.ps1 new file mode 100644 index 000000000..def193fe5 --- /dev/null +++ b/tmp-browser-smoke/form-controls/enter-submit-probe.ps1 @@ -0,0 +1,116 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\form-controls" +$port = 8154 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "form_server.py" +$browserOut = Join-Path $root "enter-submit.browser.stdout.txt" +$browserErr = Join-Path $root "enter-submit.browser.stderr.txt" +$serverOut = Join-Path $root "enter-submit.server.stdout.txt" +$serverErr = Join-Path $root "enter-submit.server.stderr.txt" +$pngPath = Join-Path $root "enter-submit.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$pngPath -Force -ErrorAction SilentlyContinue + +. (Join-Path (Split-Path $PSScriptRoot -Parent) "common\Win32Input.ps1") + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$titleBefore = $null +$titleAfterType = $null +$titleAfterSubmit = $null +$typedWorked = $false +$submittedWorked = $false +$serverSawSubmit = $false +$failure = $null + +function Wait-ForTitleLike([IntPtr]$Hwnd, [string]$Pattern, [int]$Attempts = 20, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + $title = Get-SmokeWindowTitle $Hwnd + if ($title -like $Pattern) { + return $title + } + } + return $null +} + +try { + $server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/ping" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "enter submit probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/submit.html","--window_width","420","--window_height","520","--screenshot_png",$pngPath -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $pngPath) -and ((Get-Item $pngPath).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "enter submit probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "enter submit probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + Send-SmokeText "Q" + $titleAfterType = Wait-ForTitleLike $hwnd "Enter Submit Q*" + $typedWorked = $null -ne $titleAfterType + if (-not $typedWorked) { throw "autofocus input did not receive typed text" } + + Send-SmokeEnter + $titleAfterSubmit = Wait-ForTitleLike $hwnd "Submitted Q*" + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'FORM_SUBMIT /submitted\.html\?name=Q' + } + $submittedWorked = ($null -ne $titleAfterSubmit) -or $serverSawSubmit + if (-not $submittedWorked) { throw "pressing Enter did not submit the form" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + title_before = $titleBefore + title_after_type = $titleAfterType + title_after_submit = $titleAfterSubmit + typed_worked = $typedWorked + submitted_worked = $submittedWorked + server_saw_submit = $serverSawSubmit + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/form-controls/form_server.py b/tmp-browser-smoke/form-controls/form_server.py new file mode 100644 index 000000000..c3512bff5 --- /dev/null +++ b/tmp-browser-smoke/form-controls/form_server.py @@ -0,0 +1,104 @@ +import http.server +import socketserver +import sys +import urllib.parse + + +class FormHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/ping": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/label.html": + body = ( + b"" + b"Label Smoke Base" + b"" + b"
" + b"

Click the label to toggle the checkbox.

" + b"" + b"" + b"" + b"
" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/submit.html": + body = ( + b"" + b"Enter Submit Base" + b"" + b"
" + b"
" + b"" + b"" + b"
" + b"" + b"
" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path.startswith("/submitted.html"): + sys.stderr.write("FORM_SUBMIT " + self.path + "\n") + sys.stderr.flush() + parsed = urllib.parse.urlparse(self.path) + params = urllib.parse.parse_qs(parsed.query) + name = params.get("name", [""])[0] + title = f"Submitted {name}".encode("utf-8") + body = ( + b"" + + title + + b"

Submitted

" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_error(404) + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + sys.stderr.flush() + + +def main(): + port = int(sys.argv[1]) + with socketserver.TCPServer(("127.0.0.1", port), FormHandler) as server: + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/tmp-browser-smoke/form-controls/label-click-probe.ps1 b/tmp-browser-smoke/form-controls/label-click-probe.ps1 new file mode 100644 index 000000000..be0728d4b --- /dev/null +++ b/tmp-browser-smoke/form-controls/label-click-probe.ps1 @@ -0,0 +1,105 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\form-controls" +$port = 8154 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "form_server.py" +$browserOut = Join-Path $root "label-click.browser.stdout.txt" +$browserErr = Join-Path $root "label-click.browser.stderr.txt" +$serverOut = Join-Path $root "label-click.server.stdout.txt" +$serverErr = Join-Path $root "label-click.server.stderr.txt" +$pngPath = Join-Path $root "label-click.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$pngPath -Force -ErrorAction SilentlyContinue + +. (Join-Path (Split-Path $PSScriptRoot -Parent) "common\Win32Input.ps1") + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$titleBefore = $null +$titleAfterClick = $null +$clickedWorked = $false +$failure = $null + +function Wait-ForTitleLike([IntPtr]$Hwnd, [string]$Pattern, [int]$Attempts = 20, [int]$SleepMs = 200) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + $title = Get-SmokeWindowTitle $Hwnd + if ($title -like $Pattern) { + return $title + } + } + return $null +} + +try { + $server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/ping" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "label probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/label.html","--window_width","420","--window_height","520","--screenshot_png",$pngPath -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $pngPath) -and ((Get-Item $pngPath).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "label probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "label probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + [void](Invoke-SmokeClientClick $hwnd 316 408) + $titleAfterClick = Wait-ForTitleLike $hwnd "Label Smoke true*" + if (-not $titleAfterClick) { + Start-Sleep -Milliseconds 200 + $titleAfterClick = Get-SmokeWindowTitle $hwnd + } + $clickedWorked = $titleAfterClick -like "Label Smoke true*" + if (-not $clickedWorked) { throw "label click did not toggle the checkbox" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + title_before = $titleBefore + title_after_click = $titleAfterClick + clicked_worked = $clickedWorked + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-accept-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-accept-probe.ps1 new file mode 100644 index 000000000..9aebc73c6 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-accept-probe.ps1 @@ -0,0 +1,114 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-accept" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8157 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-accept.png" +$browserOut = Join-Path $root "http-runtime-accept.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-accept.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-accept.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-accept.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/accept-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image accept server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/accept-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/accept-red.png" }) +$lastImage = if ($imageEntries.Count -gt 0) { $imageEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($lastImage) { [bool]$lastImage.allowed } else { $false } + image_user_agent = if ($lastImage) { [string]$lastImage.user_agent } else { "" } + image_accept = if ($lastImage) { [string]$lastImage.accept } else { "" } + image_referer = if ($lastImage) { [string]$lastImage.referer } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-anonymous-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-anonymous-probe.ps1 new file mode 100644 index 000000000..ed956df21 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-anonymous-probe.ps1 @@ -0,0 +1,115 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-auth-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8156 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-auth-anonymous.png" +$browserOut = Join-Path $root "http-runtime-auth-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-auth-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-auth-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-auth-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-anon-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image auth anonymous server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/auth-anon-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/auth-anon-red.png" }) +$lastImage = if ($imageEntries.Count -gt 0) { $imageEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($lastImage) { [bool]$lastImage.allowed } else { $false } + image_user_agent = if ($lastImage) { [string]$lastImage.user_agent } else { "" } + image_cookie = if ($lastImage) { [string]$lastImage.cookie } else { "" } + image_referer = if ($lastImage) { [string]$lastImage.referer } else { "" } + image_authorization = if ($lastImage) { [string]$lastImage.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-inherit-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-inherit-probe.ps1 new file mode 100644 index 000000000..096a41e7d --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-inherit-probe.ps1 @@ -0,0 +1,116 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-auth-inherit" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8158 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-auth-inherit.png" +$browserOut = Join-Path $root "http-runtime-auth-inherit.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-auth-inherit.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-auth-inherit.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-auth-inherit.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" +$pageUrl = "http://img%20user:p%40ss@127.0.0.1:$port/auth-inherit-page.html" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-inherit-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image inherited auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/auth-inherit-red.png" }) +$lastImage = if ($imageEntries.Count -gt 0) { $imageEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($lastImage) { [bool]$lastImage.allowed } else { $false } + image_user_agent = if ($lastImage) { [string]$lastImage.user_agent } else { "" } + image_cookie = if ($lastImage) { [string]$lastImage.cookie } else { "" } + image_referer = if ($lastImage) { [string]$lastImage.referer } else { "" } + image_authorization = if ($lastImage) { [string]$lastImage.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-probe.ps1 new file mode 100644 index 000000000..819ca7638 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-auth-probe.ps1 @@ -0,0 +1,115 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8155 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-auth.png" +$browserOut = Join-Path $root "http-runtime-auth.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-auth.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-auth.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-auth.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/auth-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/auth-red.png" }) +$lastImage = if ($imageEntries.Count -gt 0) { $imageEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($lastImage) { [bool]$lastImage.allowed } else { $false } + image_user_agent = if ($lastImage) { [string]$lastImage.user_agent } else { "" } + image_cookie = if ($lastImage) { [string]$lastImage.cookie } else { "" } + image_referer = if ($lastImage) { [string]$lastImage.referer } else { "" } + image_authorization = if ($lastImage) { [string]$lastImage.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-policy-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-policy-probe.ps1 new file mode 100644 index 000000000..8c55128f7 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-policy-probe.ps1 @@ -0,0 +1,114 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-policy" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8154 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-policy.png" +$browserOut = Join-Path $root "http-runtime-policy.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-policy.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-policy.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-policy.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/policy-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image policy server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/policy-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/policy-red.png" }) +$lastImage = if ($imageEntries.Count -gt 0) { $imageEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($lastImage) { [bool]$lastImage.allowed } else { $false } + image_user_agent = if ($lastImage) { [string]$lastImage.user_agent } else { "" } + image_cookie = if ($lastImage) { [string]$lastImage.cookie } else { "" } + image_referer = if ($lastImage) { [string]$lastImage.referer } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-probe.ps1 new file mode 100644 index 000000000..cf0645e12 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-probe.ps1 @@ -0,0 +1,117 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8153 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime.png" +$browserOut = Join-Path $root "http-runtime.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/img-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image runtime server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/img-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + $redBounds = [ordered]@{ min_x = $null; min_y = $null; max_x = $null; max_y = $null } + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + if ($null -eq $redBounds.min_x -or $x -lt $redBounds.min_x) { $redBounds.min_x = $x } + if ($null -eq $redBounds.min_y -or $y -lt $redBounds.min_y) { $redBounds.min_y = $y } + if ($null -eq $redBounds.max_x -or $x -gt $redBounds.max_x) { $redBounds.max_x = $x } + if ($null -eq $redBounds.max_y -or $y -gt $redBounds.max_y) { $redBounds.max_y = $y } + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + red_bounds = $redBounds + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$imageEntries = @($requestEntries | Where-Object { $_.path -eq "/red.png" }) + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + image_request_count = $imageEntries.Count + image_request_allowed = if ($imageEntries.Count -gt 0) { [bool]$imageEntries[-1].allowed } else { $false } + image_user_agent = if ($imageEntries.Count -gt 0) { [string]$imageEntries[-1].user_agent } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-redirect-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-redirect-probe.ps1 new file mode 100644 index 000000000..b1caa6b90 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-image-redirect-probe.ps1 @@ -0,0 +1,118 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-redirect" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8155 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-redirect.png" +$browserOut = Join-Path $root "http-runtime-redirect.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-redirect.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-redirect.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-redirect.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/redirect-policy-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost image redirect server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/redirect-policy-page.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $redCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 180 -and $c.G -le 80 -and $c.B -le 80) { + $redCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + red_count = $redCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$firstHop = @($requestEntries | Where-Object { $_.path -eq "/redirect-one.png" }) +$finalHop = @($requestEntries | Where-Object { $_.path -eq "/redirect-final.png" }) +$lastFirstHop = if ($firstHop.Count -gt 0) { $firstHop[-1] } else { $null } +$lastFinalHop = if ($finalHop.Count -gt 0) { $finalHop[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + redirect_first_count = $firstHop.Count + redirect_final_count = $finalHop.Count + redirect_first_cookie = if ($lastFirstHop) { [string]$lastFirstHop.cookie } else { "" } + redirect_first_referer = if ($lastFirstHop) { [string]$lastFirstHop.referer } else { "" } + redirect_final_cookie = if ($lastFinalHop) { [string]$lastFinalHop.cookie } else { "" } + redirect_final_allowed = if ($lastFinalHop) { [bool]$lastFinalHop.allowed } else { $false } + redirect_final_user_agent = if ($lastFinalHop) { [string]$lastFinalHop.user_agent } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-anonymous-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-anonymous-probe.ps1 new file mode 100644 index 000000000..c1feb4b5c --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-anonymous-probe.ps1 @@ -0,0 +1,91 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-module-auth-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8163 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-module-auth-anonymous.png" +$browserOut = Join-Path $root "http-runtime-module-auth-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-module-auth-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-module-auth-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-module-auth-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" +$pageUrl = "http://img%20user:p%40ss@127.0.0.1:$port/auth-module-anonymous-page.html" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-module-anonymous-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost anonymous module auth server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } +} + +$entries = @() +if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$rootEntries = @($entries | Where-Object { $_.path -eq "/auth-module-anon-root.js" }) +$childEntries = @($entries | Where-Object { $_.path -eq "/auth-module-anon-child.js" }) +$beaconEntries = @($entries | Where-Object { $_.path -eq "/module-anon-beacon.png" }) +$lastRoot = if ($rootEntries.Count -gt 0) { $rootEntries[-1] } else { $null } +$lastChild = if ($childEntries.Count -gt 0) { $childEntries[-1] } else { $null } +$lastBeacon = if ($beaconEntries.Count -gt 0) { $beaconEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + root_request_count = $rootEntries.Count + root_request_allowed = if ($lastRoot) { [bool]$lastRoot.allowed } else { $false } + root_cookie = if ($lastRoot) { [string]$lastRoot.cookie } else { "" } + root_referer = if ($lastRoot) { [string]$lastRoot.referer } else { "" } + root_authorization = if ($lastRoot) { [string]$lastRoot.authorization } else { "" } + child_request_count = $childEntries.Count + child_request_allowed = if ($lastChild) { [bool]$lastChild.allowed } else { $false } + child_cookie = if ($lastChild) { [string]$lastChild.cookie } else { "" } + child_referer = if ($lastChild) { [string]$lastChild.referer } else { "" } + child_authorization = if ($lastChild) { [string]$lastChild.authorization } else { "" } + beacon_request_count = $beaconEntries.Count + beacon_request_allowed = if ($lastBeacon) { [bool]$lastBeacon.allowed } else { $false } + beacon_cookie = if ($lastBeacon) { [string]$lastBeacon.cookie } else { "" } + beacon_referer = if ($lastBeacon) { [string]$lastBeacon.referer } else { "" } + beacon_authorization = if ($lastBeacon) { [string]$lastBeacon.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-probe.ps1 new file mode 100644 index 000000000..ae9bca51a --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-module-auth-probe.ps1 @@ -0,0 +1,88 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-module-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8162 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-module-auth.png" +$browserOut = Join-Path $root "http-runtime-module-auth.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-module-auth.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-module-auth.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-module-auth.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" +$pageUrl = "http://img%20user:p%40ss@127.0.0.1:$port/auth-module-page.html" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-module-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost module auth server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } +} + +$entries = @() +if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$rootEntries = @($entries | Where-Object { $_.path -eq "/auth-module-root.js" }) +$childEntries = @($entries | Where-Object { $_.path -eq "/auth-module-child.js" }) +$beaconEntries = @($entries | Where-Object { $_.path -eq "/module-auth-beacon.png" }) +$lastRoot = if ($rootEntries.Count -gt 0) { $rootEntries[-1] } else { $null } +$lastChild = if ($childEntries.Count -gt 0) { $childEntries[-1] } else { $null } +$lastBeacon = if ($beaconEntries.Count -gt 0) { $beaconEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + root_request_count = $rootEntries.Count + root_request_allowed = if ($lastRoot) { [bool]$lastRoot.allowed } else { $false } + root_cookie = if ($lastRoot) { [string]$lastRoot.cookie } else { "" } + root_referer = if ($lastRoot) { [string]$lastRoot.referer } else { "" } + root_authorization = if ($lastRoot) { [string]$lastRoot.authorization } else { "" } + child_request_count = $childEntries.Count + child_request_allowed = if ($lastChild) { [bool]$lastChild.allowed } else { $false } + child_cookie = if ($lastChild) { [string]$lastChild.cookie } else { "" } + child_referer = if ($lastChild) { [string]$lastChild.referer } else { "" } + child_authorization = if ($lastChild) { [string]$lastChild.authorization } else { "" } + beacon_request_count = $beaconEntries.Count + beacon_request_allowed = if ($lastBeacon) { [bool]$lastBeacon.allowed } else { $false } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-anonymous-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-anonymous-probe.ps1 new file mode 100644 index 000000000..9cfcec66f --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-anonymous-probe.ps1 @@ -0,0 +1,124 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-script-auth-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8161 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-script-auth-anonymous.png" +$browserOut = Join-Path $root "http-runtime-script-auth-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-script-auth-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-script-auth-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-script-auth-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" +$pageUrl = "http://img%20user:p%40ss@127.0.0.1:$port/auth-script-anonymous-page.html" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-script-anonymous-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost anonymous script auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $blueCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.B -ge 160 -and $c.G -ge 80 -and $c.R -le 80) { + $blueCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + blue_count = $blueCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$scriptEntries = @($requestEntries | Where-Object { $_.path -eq "/auth-anon-script.js" }) +$lastScript = if ($scriptEntries.Count -gt 0) { $scriptEntries[-1] } else { $null } +$beaconEntries = @($requestEntries | Where-Object { $_.path -eq "/script-anon-beacon.png" }) +$lastBeacon = if ($beaconEntries.Count -gt 0) { $beaconEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + script_request_count = $scriptEntries.Count + script_request_allowed = if ($lastScript) { [bool]$lastScript.allowed } else { $false } + script_user_agent = if ($lastScript) { [string]$lastScript.user_agent } else { "" } + script_cookie = if ($lastScript) { [string]$lastScript.cookie } else { "" } + script_referer = if ($lastScript) { [string]$lastScript.referer } else { "" } + script_authorization = if ($lastScript) { [string]$lastScript.authorization } else { "" } + script_accept = if ($lastScript) { [string]$lastScript.accept } else { "" } + beacon_request_count = $beaconEntries.Count + beacon_request_allowed = if ($lastBeacon) { [bool]$lastBeacon.allowed } else { $false } + beacon_cookie = if ($lastBeacon) { [string]$lastBeacon.cookie } else { "" } + beacon_referer = if ($lastBeacon) { [string]$lastBeacon.referer } else { "" } + beacon_authorization = if ($lastBeacon) { [string]$lastBeacon.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-probe.ps1 b/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-probe.ps1 new file mode 100644 index 000000000..4b3a5cb6c --- /dev/null +++ b/tmp-browser-smoke/image-smoke/chrome-http-runtime-script-auth-probe.ps1 @@ -0,0 +1,123 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\image-smoke" +$profileRoot = Join-Path $root "profile-http-runtime-script-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8159 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "http_runtime_server.py" +$outPng = Join-Path $root "http-runtime-script-auth.png" +$browserOut = Join-Path $root "http-runtime-script-auth.browser.stdout.txt" +$browserErr = Join-Path $root "http-runtime-script-auth.browser.stderr.txt" +$serverOut = Join-Path $root "http-runtime-script-auth.server.stdout.txt" +$serverErr = Join-Path $root "http-runtime-script-auth.server.stderr.txt" +$requestLog = Join-Path $root "http-runtime.requests.jsonl" +$pageUrl = "http://img%20user:p%40ss@127.0.0.1:$port/auth-script-page.html" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-script-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost script auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { + $pngReady = $true + break + } +} + +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $greenCount = 0 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.G -ge 150 -and $c.R -le 80 -and $c.B -le 120) { + $greenCount++ + } + } + } + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + green_count = $greenCount + } + } finally { + $bmp.Dispose() + } +} + +$requestEntries = @() +if (Test-Path $requestLog) { + $requestEntries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } +} +$scriptEntries = @($requestEntries | Where-Object { $_.path -eq "/auth-inherit-script.js" }) +$lastScript = if ($scriptEntries.Count -gt 0) { $scriptEntries[-1] } else { $null } +$beaconEntries = @($requestEntries | Where-Object { $_.path -eq "/script-beacon.png" }) +$lastBeacon = if ($beaconEntries.Count -gt 0) { $beaconEntries[-1] } else { $null } + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + script_request_count = $scriptEntries.Count + script_request_allowed = if ($lastScript) { [bool]$lastScript.allowed } else { $false } + script_user_agent = if ($lastScript) { [string]$lastScript.user_agent } else { "" } + script_cookie = if ($lastScript) { [string]$lastScript.cookie } else { "" } + script_referer = if ($lastScript) { [string]$lastScript.referer } else { "" } + script_authorization = if ($lastScript) { [string]$lastScript.authorization } else { "" } + beacon_request_count = $beaconEntries.Count + beacon_request_allowed = if ($lastBeacon) { [bool]$lastBeacon.allowed } else { $false } + beacon_cookie = if ($lastBeacon) { [string]$lastBeacon.cookie } else { "" } + beacon_referer = if ($lastBeacon) { [string]$lastBeacon.referer } else { "" } + beacon_authorization = if ($lastBeacon) { [string]$lastBeacon.authorization } else { "" } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/image-smoke/http_runtime_server.py b/tmp-browser-smoke/image-smoke/http_runtime_server.py new file mode 100644 index 000000000..f24fdf705 --- /dev/null +++ b/tmp-browser-smoke/image-smoke/http_runtime_server.py @@ -0,0 +1,846 @@ +import json +import sys +from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent +REQUEST_LOG = ROOT / "http-runtime.requests.jsonl" +PORT = 8153 + + +def append_log(entry: dict) -> None: + with REQUEST_LOG.open("a", encoding="utf-8") as fh: + fh.write(json.dumps(entry) + "\n") + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + if self.path == "/" or self.path == "/img-page.html": + body = (ROOT / "img-page.html").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/policy-page.html": + body = b""" + +Image Policy Smoke +policy test + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpimg=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/redirect-policy-page.html": + body = b""" + +Image Redirect Policy Smoke +redirect policy test + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "page=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-page.html": + body = f""" + +Image Auth Policy Smoke +auth policy test + +""".encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpimgauth=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-anon-page.html": + body = f""" + +Image Auth Anonymous Smoke +auth anonymous policy test + +""".encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpimganon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-inherit-page.html": + body = b""" + +Image Inherited Auth Smoke +inherited auth policy test + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpimgauthinherit=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-script-page.html": + body = b""" + +Script Auth Smoke + + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpscriptauth=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-script-anonymous-page.html": + body = f""" + +Script Anonymous Smoke + + +""".encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpscriptanon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-page.html": + body = b""" + +Module Auth Smoke + + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpmoduleauth=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-anonymous-page.html": + body = f""" + +Module Anonymous Smoke + + +""".encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpmoduleanon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/accept-page.html": + body = b""" + +Image Accept Policy Smoke +accept policy test + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/red.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + allowed = "Lightpanda/" in ua + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/policy-red.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + expected_referer = f"http://127.0.0.1:{PORT}/policy-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpimg=ok" in cookie + and referer == expected_referer + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/redirect-one.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "allowed": "Lightpanda/" in ua and "page=ok" in cookie, + }) + self.send_response(302) + self.send_header("Location", "/redirect-final.png") + self.send_header("Set-Cookie", "redirect=ok; Path=/") + self.end_headers() + return + + if self.path == "/redirect-final.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + allowed = ( + "Lightpanda/" in ua + and "page=ok" in cookie + and "redirect=ok" in cookie + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-red.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpimgauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-anon-red.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-anon-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/accept-red.png": + ua = self.headers.get("User-Agent", "") + accept = self.headers.get("Accept", "") + referer = self.headers.get("Referer", "") + expected_referer = f"http://127.0.0.1:{PORT}/accept-page.html" + allowed = ( + "Lightpanda/" in ua + and "image/avif" in accept + and "image/webp" in accept + and "image/*" in accept + and "*/*;q=0.8" in accept + and referer == expected_referer + ) + append_log({ + "path": self.path, + "user_agent": ua, + "accept": accept, + "referer": referer, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-inherit-red.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-inherit-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpimgauthinherit=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-inherit-script.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-script-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpscriptauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"document.title='Script Auth Blocked';" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"""document.title='Script Auth OK';(function(){var panel=document.createElement('div');panel.id='script-auth-panel';panel.textContent='script auth ok';panel.setAttribute('style','width:320px;height:160px;background:#18c23e;color:#ffffff;padding:24px;font-size:28px;');document.body.innerHTML='';document.body.appendChild(panel);var img=new Image();img.alt='script beacon';img.src='/script-beacon.png';document.body.appendChild(img);}());""" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-anon-script.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-script-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "*/*" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"document.title='Script Anonymous Blocked';" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"""document.title='Script Anonymous OK';(function(){var panel=document.createElement('div');panel.id='script-auth-anon-panel';panel.textContent='script anonymous ok';panel.setAttribute('style','width:320px;height:160px;background:#147ad6;color:#ffffff;padding:24px;font-size:28px;');document.body.innerHTML='';document.body.appendChild(panel);var img=new Image();img.alt='script anonymous beacon';img.src='/script-anon-beacon.png';document.body.appendChild(img);}());""" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-root.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpmoduleauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + and "*/*" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"throw new Error('blocked root module');" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"import { mountModuleAuth } from './auth-module-child.js'; mountModuleAuth();" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-child.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-root.js" + allowed = ( + "Lightpanda/" in ua + and "lpmoduleauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + and "*/*" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"throw new Error('blocked child module');" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"""export function mountModuleAuth(){document.title='Module Auth OK';var panel=document.createElement('div');panel.id='module-auth-panel';panel.textContent='module auth ok';panel.setAttribute('style','width:320px;height:160px;background:#18c23e;color:#ffffff;padding:24px;font-size:28px;');document.body.innerHTML='';document.body.appendChild(panel);var img=new Image();img.alt='module auth beacon';img.src='/module-auth-beacon.png';document.body.appendChild(img);}""" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-anon-root.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "*/*" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"throw new Error('blocked anonymous root module');" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"import { mountModuleAnonymous } from './auth-module-anon-child.js'; mountModuleAnonymous();" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-module-anon-child.js": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-anon-root.js" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "*/*" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"throw new Error('blocked anonymous child module');" + self.send_response(403) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"""export function mountModuleAnonymous(){document.title='Module Anonymous OK';var panel=document.createElement('div');panel.id='module-anon-panel';panel.textContent='module anonymous ok';panel.setAttribute('style','width:320px;height:160px;background:#147ad6;color:#ffffff;padding:24px;font-size:28px;');document.body.innerHTML='';document.body.appendChild(panel);var img=new Image();img.alt='module anonymous beacon';img.src='/module-anon-beacon.png';document.body.appendChild(img);}""" + self.send_response(200) + self.send_header("Content-Type", "application/javascript; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/script-beacon.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-script-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpscriptauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/script-anon-beacon.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-script-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpscriptanon=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/module-auth-beacon.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpmoduleauth=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/module-anon-beacon.png": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-module-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpmoduleanon=ok" in cookie + and referer == expected_referer + and authorization == "Basic aW1nIHVzZXI6cEBzcw==" + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "allowed": allowed, + }) + if not allowed: + body = b"blocked" + self.send_response(403) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = (ROOT / "red.png").read_bytes() + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_response(404) + self.end_headers() + + def log_message(self, fmt: str, *args) -> None: + return + + +def main() -> int: + global PORT + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8153 + PORT = port + if REQUEST_LOG.exists(): + REQUEST_LOG.unlink() + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + print(f"READY {port}", flush=True) + try: + server.serve_forever() + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/image-smoke/img-page.html b/tmp-browser-smoke/image-smoke/img-page.html new file mode 100644 index 000000000..1be7c492c --- /dev/null +++ b/tmp-browser-smoke/image-smoke/img-page.html @@ -0,0 +1,14 @@ + + + + + Image Smoke + + + + red test + + diff --git a/tmp-browser-smoke/image-smoke/red.png b/tmp-browser-smoke/image-smoke/red.png new file mode 100644 index 000000000..1bc71ca05 Binary files /dev/null and b/tmp-browser-smoke/image-smoke/red.png differ diff --git a/tmp-browser-smoke/indexeddb-persistence/IndexedDbProbeCommon.ps1 b/tmp-browser-smoke/indexeddb-persistence/IndexedDbProbeCommon.ps1 new file mode 100644 index 000000000..3f12fdb47 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/IndexedDbProbeCommon.ps1 @@ -0,0 +1,170 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\indexeddb-persistence" +$script:BrowserExe = if ($env:LIGHTPANDA_BROWSER_EXE) { $env:LIGHTPANDA_BROWSER_EXE } else { Join-Path $script:Repo "zig-out\bin\lightpanda.exe" } + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-IndexedDbProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + IndexedDbFile = Join-Path $appDataRoot "indexed-db-v1.txt" + SettingsFile = Join-Path $appDataRoot "browse-settings-v1.txt" + } +} + +function Seed-IndexedDbProfile([string]$AppDataRoot) { +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Get-FreeIndexedDbPort() { + $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) + $listener.Start() + try { + return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port + } finally { + $listener.Stop() + } +} + +function Wait-IndexedDbServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/seed.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-IndexedDbServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "indexeddb_server.py"),"$Port" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Start-IndexedDbBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Invoke-IndexedDbAddressCommit([IntPtr]$Hwnd, [string]$Url) { + Show-SmokeWindow $Hwnd + Start-Sleep -Milliseconds 120 + Send-SmokeCtrlL + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 120 + Send-SmokeText $Url + Start-Sleep -Milliseconds 120 + Send-SmokeEnter +} + +function Invoke-IndexedDbAddressNavigate([IntPtr]$Hwnd, [int]$BrowserId, [string]$Url, [string]$Needle) { + Invoke-IndexedDbAddressCommit $Hwnd $Url + return Wait-TabTitle $BrowserId $Needle 40 +} + +function Read-IndexedDbFileData([string]$IndexedDbFile) { + if (-not (Test-Path $IndexedDbFile)) { + return "" + } + return Get-Content $IndexedDbFile -Raw +} + +function ConvertTo-IndexedDbEntryPattern([string]$Origin, [string]$DatabaseName, [string]$StoreName, [string]$Key, [string]$Json) { + $originField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Origin)) + $dbField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($DatabaseName)) + $storeField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($StoreName)) + $keyField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Key)) + $valueField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Json)) + return [regex]::Escape("entry`t$originField`t$dbField`t$storeField`t$keyField`t$valueField") +} + +function ConvertTo-IndexedDbIndexPattern([string]$Origin, [string]$DatabaseName, [string]$StoreName, [string]$IndexName, [string]$KeyPath) { + $originField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Origin)) + $dbField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($DatabaseName)) + $storeField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($StoreName)) + $indexField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($IndexName)) + $keyPathField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($KeyPath)) + return [regex]::Escape("index`t$originField`t$dbField`t$storeField`t$indexField`t$keyPathField") +} + +function Wait-IndexedDbFileMatch([string]$IndexedDbFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-IndexedDbFileData $IndexedDbFile + if ($data -match $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Wait-IndexedDbFileNoMatch([string]$IndexedDbFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-IndexedDbFileData $IndexedDbFile + if ($data -notmatch $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Wait-OwnedIndexedDbProbeProcessGone([int]$ProcessId, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + if (-not (Get-Process -Id $ProcessId -ErrorAction SilentlyContinue)) { + return $true + } + Start-Sleep -Milliseconds 150 + } + return $false +} + +function Format-IndexedDbProbeProcessMeta($Meta) { + if (-not $Meta) { + return $null + } + + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-IndexedDbProbeResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + + if ($value -is [System.Collections.IDictionary]) { + Write-IndexedDbProbeResult $value "$key." + continue + } + + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-clear-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-clear-probe.ps1 new file mode 100644 index 000000000..4388949a1 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-clear-probe.ps1 @@ -0,0 +1,120 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-clear" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-IndexedDbEntryPattern $origin "lp-persist" "items" "persist" '{"status":"ok"}' +$browserOneOut = Join-Path $Root "chrome-indexeddb-clear.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-indexeddb-clear.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-indexeddb-clear.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-indexeddb-clear.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-clear.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-clear.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$settingsOpened = $false +$clearInvoked = $false +$clearedOnDisk = $false +$missingAfterClear = $false +$missingAfterRestart = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$indexedDbData = "" + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb server did not become ready" } + + $browserOne = Start-IndexedDbBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "indexeddb clear run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "IndexedDB Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish indexeddb write" } + + $indexedDbData = Wait-IndexedDbFileMatch $app.IndexedDbFile $entryPattern + if (-not $indexedDbData) { throw "indexeddb data did not persist before clear" } + + $titles.settings = Invoke-IndexedDbAddressNavigate $hwndOne $browserOne.Id "browser://settings" "Browser Settings" + $settingsOpened = [bool]$titles.settings + if (-not $settingsOpened) { throw "browser://settings did not load" } + + $titles.settings_after_clear = Invoke-IndexedDbAddressNavigate $hwndOne $browserOne.Id "browser://settings/clear-indexed-db" "Browser Settings" + $clearInvoked = [bool]$titles.settings_after_clear + if (-not $clearInvoked) { throw "clear indexeddb action did not return to settings page" } + + $indexedDbData = Wait-IndexedDbFileNoMatch $app.IndexedDbFile $entryPattern + $clearedOnDisk = [bool]$indexedDbData + if (-not $clearedOnDisk) { throw "indexeddb persisted file still contains cleared entry" } + + $titles.echo_missing = Invoke-IndexedDbAddressNavigate $hwndOne $browserOne.Id "$origin/echo.html" "IndexedDB Echo missing" + $missingAfterClear = [bool]$titles.echo_missing + if (-not $missingAfterClear) { throw "indexeddb remained visible after clear" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedIndexedDbProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-IndexedDbBrowser -StartupUrl "$origin/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "indexeddb clear run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart_missing = Wait-TabTitle $browserTwo.Id "IndexedDB Echo missing" 40 + $missingAfterRestart = [bool]$titles.restart_missing + if (-not $missingAfterRestart) { throw "indexeddb remained present after restart" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $indexedDbData) { $indexedDbData = Read-IndexedDbFileData $app.IndexedDbFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + settings_opened = $settingsOpened + clear_invoked = $clearInvoked + cleared_on_disk = $clearedOnDisk + missing_after_clear = $missingAfterClear + missing_after_restart = $missingAfterRestart + titles = $titles + indexed_db_file = $indexedDbData + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_one_meta = Format-IndexedDbProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-IndexedDbProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $seedWorked -or -not $settingsOpened -or -not $clearInvoked -or -not $clearedOnDisk -or -not $missingAfterClear -or -not $missingAfterRestart) { + exit 1 + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cross-tab-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cross-tab-probe.ps1 new file mode 100644 index 000000000..28776b593 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cross-tab-probe.ps1 @@ -0,0 +1,85 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-cross-tab" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-IndexedDbEntryPattern $origin "lp-persist" "items" "persist" '{"status":"ok"}' +$browserOut = Join-Path $Root "chrome-indexeddb-cross-tab.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-indexeddb-cross-tab.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-cross-tab.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-cross-tab.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$persistedToDisk = $false +$echoWorked = $false +$seedTabRetained = $false +$failure = $null +$titles = [ordered]@{} +$indexedDbData = "" + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb server did not become ready" } + + $browser = Start-IndexedDbBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "indexeddb cross-tab window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-TabTitle $browser.Id "IndexedDB Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish indexeddb write" } + + $indexedDbData = Wait-IndexedDbFileMatch $app.IndexedDbFile $entryPattern + $persistedToDisk = [bool]$indexedDbData + if (-not $persistedToDisk) { throw "indexeddb data did not persist to disk before cross-tab check" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-IndexedDbAddressNavigate $hwnd $browser.Id "$origin/echo.html" "IndexedDB Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "new tab did not see shared indexeddb data" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-TabTitle $browser.Id "IndexedDB Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "seed tab was not preserved after cross-tab check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + persisted_to_disk = $persistedToDisk + echo_worked = $echoWorked + seed_tab_retained = $seedTabRetained + indexed_db_file = if ($indexedDbData) { $indexedDbData } else { Read-IndexedDbFileData $app.IndexedDbFile } + titles = $titles + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_meta = Format-IndexedDbProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $seedWorked -or -not $persistedToDisk -or -not $echoWorked -or -not $seedTabRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cursor-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cursor-probe.ps1 new file mode 100644 index 000000000..0ce6c75c9 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-cursor-probe.ps1 @@ -0,0 +1,76 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-cursor" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$browserOut = Join-Path $Root "chrome-indexeddb-cursor.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-indexeddb-cursor.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-cursor.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-cursor.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$echoWorked = $false +$seedTabRetained = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb cursor server did not become ready" } + + $browser = Start-IndexedDbBrowser -StartupUrl "$origin/cursor-seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "indexeddb cursor window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-TabTitle $browser.Id "IndexedDB Cursor Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "cursor seed page did not finish indexeddb writes" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-IndexedDbAddressNavigate $hwnd $browser.Id "$origin/cursor-echo.html" "IndexedDB Cursor Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "new tab did not resolve indexeddb cursor iteration" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-TabTitle $browser.Id "IndexedDB Cursor Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "cursor seed tab was not preserved after cursor echo check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + echo_worked = $echoWorked + seed_tab_retained = $seedTabRetained + titles = $titles + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_meta = Format-IndexedDbProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $seedWorked -or -not $echoWorked -or -not $seedTabRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-index-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-index-probe.ps1 new file mode 100644 index 000000000..c45189f43 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-index-probe.ps1 @@ -0,0 +1,92 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-index" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$indexPattern = ConvertTo-IndexedDbIndexPattern $origin "lp-index-persist" "users" "by_email" "email" +$entryPattern = ConvertTo-IndexedDbEntryPattern $origin "lp-index-persist" "users" "user-1" '{"status":"ok","email":"ada@example.com"}' +$browserOut = Join-Path $Root "chrome-indexeddb-index.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-indexeddb-index.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-index.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-index.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$indexPersisted = $false +$entryPersisted = $false +$echoWorked = $false +$seedTabRetained = $false +$failure = $null +$titles = [ordered]@{} +$indexedDbData = "" + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb index server did not become ready" } + + $browser = Start-IndexedDbBrowser -StartupUrl "$origin/index-seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "indexeddb index window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-TabTitle $browser.Id "IndexedDB Index Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "index seed page did not finish indexeddb write" } + + $indexedDbData = Wait-IndexedDbFileMatch $app.IndexedDbFile $indexPattern + $indexPersisted = [bool]$indexedDbData + if (-not $indexPersisted) { throw "indexeddb index metadata did not persist to disk" } + + $indexedDbData = Wait-IndexedDbFileMatch $app.IndexedDbFile $entryPattern + $entryPersisted = [bool]$indexedDbData + if (-not $entryPersisted) { throw "indexeddb indexed entry did not persist to disk" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-IndexedDbAddressNavigate $hwnd $browser.Id "$origin/index-echo.html" "IndexedDB Index Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "new tab did not resolve indexeddb lookup through persisted index" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-TabTitle $browser.Id "IndexedDB Index Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "index seed tab was not preserved after index echo check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + index_persisted = $indexPersisted + entry_persisted = $entryPersisted + echo_worked = $echoWorked + seed_tab_retained = $seedTabRetained + indexed_db_file = if ($indexedDbData) { $indexedDbData } else { Read-IndexedDbFileData $app.IndexedDbFile } + titles = $titles + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_meta = Format-IndexedDbProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $seedWorked -or -not $indexPersisted -or -not $entryPersisted -or -not $echoWorked -or -not $seedTabRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-restart-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-restart-probe.ps1 new file mode 100644 index 000000000..cbc390e18 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-restart-probe.ps1 @@ -0,0 +1,99 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-restart" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-IndexedDbEntryPattern $origin "lp-persist" "items" "persist" '{"status":"ok"}' +$browserOneOut = Join-Path $Root "chrome-indexeddb-restart.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-indexeddb-restart.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-indexeddb-restart.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-indexeddb-restart.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-restart.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-restart.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$persistedToDisk = $false +$restartWorked = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$indexedDbData = "" + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb server did not become ready" } + + $browserOne = Start-IndexedDbBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "indexeddb restart run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "IndexedDB Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish indexeddb write" } + + $indexedDbData = Wait-IndexedDbFileMatch $app.IndexedDbFile $entryPattern + $persistedToDisk = [bool]$indexedDbData + if (-not $persistedToDisk) { throw "indexeddb data was not persisted before restart" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedIndexedDbProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-IndexedDbBrowser -StartupUrl "$origin/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "indexeddb restart run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart = Wait-TabTitle $browserTwo.Id "IndexedDB Echo ok" 40 + $restartWorked = [bool]$titles.restart + if (-not $restartWorked) { throw "restarted browser did not reuse persisted indexeddb data" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $indexedDbData) { $indexedDbData = Read-IndexedDbFileData $app.IndexedDbFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + persisted_to_disk = $persistedToDisk + restart_worked = $restartWorked + titles = $titles + indexed_db_file = $indexedDbData + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_one_meta = Format-IndexedDbProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-IndexedDbProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $seedWorked -or -not $persistedToDisk -or -not $restartWorked) { + exit 1 + } +} diff --git a/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-transaction-mode-probe.ps1 b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-transaction-mode-probe.ps1 new file mode 100644 index 000000000..3b9da7203 --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/chrome-indexeddb-transaction-mode-probe.ps1 @@ -0,0 +1,63 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\indexeddb-persistence\IndexedDbProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-indexeddb-transaction-mode" +$app = Reset-IndexedDbProfile $profileRoot +Seed-IndexedDbProfile $app.AppDataRoot +$port = Get-FreeIndexedDbPort +$origin = "http://127.0.0.1:$port" +$browserOut = Join-Path $Root "chrome-indexeddb-transaction-mode.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-indexeddb-transaction-mode.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-indexeddb-transaction-mode.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-indexeddb-transaction-mode.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$modeWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-IndexedDbServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-IndexedDbServer -Port $port + if (-not $ready) { throw "indexeddb transaction mode server did not become ready" } + + $browser = Start-IndexedDbBrowser -StartupUrl "$origin/transaction-mode.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "indexeddb transaction mode window handle not found" } + Show-SmokeWindow $hwnd + + $titles.mode = Wait-TabTitle $browser.Id "IndexedDB Transaction Mode ok" 40 + $modeWorked = [bool]$titles.mode + if (-not $modeWorked) { throw "indexeddb transaction mode page did not resolve expected title" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + mode_worked = $modeWorked + titles = $titles + error = $failure + server_meta = Format-IndexedDbProbeProcessMeta $serverMeta + browser_meta = Format-IndexedDbProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-IndexedDbProbeResult $result + + if ($failure -or -not $modeWorked) { + exit 1 + } +} + +exit 0 diff --git a/tmp-browser-smoke/indexeddb-persistence/indexeddb_server.py b/tmp-browser-smoke/indexeddb-persistence/indexeddb_server.py new file mode 100644 index 000000000..d36ab5bab --- /dev/null +++ b/tmp-browser-smoke/indexeddb-persistence/indexeddb_server.py @@ -0,0 +1,256 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys + + +def html(title: str, body: str) -> bytes: + return ( + "" + f"{title}{body}" + ).encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + server_version = "IndexedDbSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def do_GET(self): + if self.path == "/favicon.ico": + self.send_response(204) + self.end_headers() + return + + if self.path == "/seed.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

seed

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/echo.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

echo

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/index-seed.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

index-seed

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/index-echo.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

index-echo

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/cursor-seed.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

cursor-seed

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/cursor-echo.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

cursor-echo

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/transaction-mode.html": + body = html( + "IndexedDB Loading - Lightpanda Browser", + "

transaction-mode

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = html("Not Found - Lightpanda Browser", "

Not Found

") + self.send_response(404) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + +def main() -> int: + port = 8210 + if len(sys.argv) > 1: + port = int(sys.argv[1]) + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/inline-flow/break.html b/tmp-browser-smoke/inline-flow/break.html new file mode 100644 index 000000000..1714a1dfd --- /dev/null +++ b/tmp-browser-smoke/inline-flow/break.html @@ -0,0 +1,21 @@ + + + + + Break Inline Flow + + +
+

+ Prefix text + RED +
+ After break + GREEN + and + LINK now +

+

Below paragraph stays below break flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/button-keyboard.html b/tmp-browser-smoke/inline-flow/button-keyboard.html new file mode 100644 index 000000000..4d9587e2f --- /dev/null +++ b/tmp-browser-smoke/inline-flow/button-keyboard.html @@ -0,0 +1,31 @@ + + + + + Wrapped Inline Button Keyboard + + + +
+

+ Lead text before the inline control + + trailing text after it +

+

Below paragraph stays below the wrapped inline control.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/button.html b/tmp-browser-smoke/inline-flow/button.html new file mode 100644 index 000000000..ed24d6774 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/button.html @@ -0,0 +1,24 @@ + + + + + Wrapped Inline Button + + +
+

+ Lead text before the inline control + + trailing text after it +

+

Below paragraph stays below the wrapped inline control.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-button-link.html b/tmp-browser-smoke/inline-flow/checkbox-button-link.html new file mode 100644 index 000000000..bc755de51 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-button-link.html @@ -0,0 +1,35 @@ + + + + + Mixed Inline Checkbox Button Link + + +
+

+ Lead text before the checkbox + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox, button, and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-link.html b/tmp-browser-smoke/inline-flow/checkbox-link.html new file mode 100644 index 000000000..b32602724 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-link.html @@ -0,0 +1,26 @@ + + + + + Mixed Inline Checkbox Link + + +
+

+ Lead text before the checkbox + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-pair-button-link.html b/tmp-browser-smoke/inline-flow/checkbox-pair-button-link.html new file mode 100644 index 000000000..96fdbef2e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-pair-button-link.html @@ -0,0 +1,44 @@ + + + + + Mixed Inline Checkbox Pair Button Link + + +
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox pair, button, and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-pair-input-submit.html b/tmp-browser-smoke/inline-flow/checkbox-pair-input-submit.html new file mode 100644 index 000000000..24d3e1ea8 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-pair-input-submit.html @@ -0,0 +1,51 @@ + + + + + Mixed Inline Checkbox Pair Input Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, input, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-pair-submit.html b/tmp-browser-smoke/inline-flow/checkbox-pair-submit.html new file mode 100644 index 000000000..4dbb95ce2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-pair-submit.html @@ -0,0 +1,42 @@ + + + + + Mixed Inline Checkbox Pair Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-button-link.html b/tmp-browser-smoke/inline-flow/checkbox-radio-button-link.html new file mode 100644 index 000000000..85a19cac1 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-button-link.html @@ -0,0 +1,45 @@ + + + + + Mixed Inline Checkbox Radio Button Link + + +
+

+ Lead text before the checkbox + + bridge words before the radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the checkbox, radio, button, and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-input-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-input-submit.html new file mode 100644 index 000000000..3284c163e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-input-submit.html @@ -0,0 +1,51 @@ + + + + + Mixed Inline Checkbox Radio Input Submit + + +
+
+

+ Lead text before the checkbox + + bridge words before the radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox, radio, input, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-input-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-input-submit.html new file mode 100644 index 000000000..5a2555a20 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-input-submit.html @@ -0,0 +1,69 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Input Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, input, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-eight-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-eight-link-submit.html new file mode 100644 index 000000000..00e6f5586 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-eight-link-submit.html @@ -0,0 +1,46 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Eight Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before the seventh link + Next Seven + bridge words before the eighth link + Next Eight + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, eight links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-five-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-five-link-submit.html new file mode 100644 index 000000000..f78dcb8fb --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-five-link-submit.html @@ -0,0 +1,40 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Five Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, five links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-four-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-four-link-submit.html new file mode 100644 index 000000000..cefe9d696 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-four-link-submit.html @@ -0,0 +1,38 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Four Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, four links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-seven-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-seven-link-submit.html new file mode 100644 index 000000000..6154ac95c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-seven-link-submit.html @@ -0,0 +1,44 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Seven Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before the seventh link + Next Seven + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, seven links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-six-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-six-link-submit.html new file mode 100644 index 000000000..26edef7a6 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-six-link-submit.html @@ -0,0 +1,42 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Six Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before the fourth link + Next Four + bridge words before the fifth link + Next Five + bridge words before the sixth link + Next Six + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, six links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit-link.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit-link.html new file mode 100644 index 000000000..2bd7f7984 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit-link.html @@ -0,0 +1,32 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Submit Link + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the link + Next + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, link, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit.html new file mode 100644 index 000000000..2b9aaaaee --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-submit.html @@ -0,0 +1,30 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-three-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-three-link-submit.html new file mode 100644 index 000000000..e54b7f912 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-three-link-submit.html @@ -0,0 +1,36 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Three Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before the third link + Next Three + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, three links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-two-link-submit.html b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-two-link-submit.html new file mode 100644 index 000000000..bc0d9edaf --- /dev/null +++ b/tmp-browser-smoke/inline-flow/checkbox-radio-pair-two-input-two-link-submit.html @@ -0,0 +1,34 @@ + + + + + Mixed Inline Checkbox Pair Radio Pair Two Input Two Link Submit + + +
+
+

+ Lead text before the first checkbox + + bridge words before the second checkbox + + bridge words before the first radio + + bridge words before the second radio + + bridge words before the first input + + bridge words before the second input + + bridge words before the first link + Next One + bridge words before the second link + Next Two + bridge words before submit + +

+
+

Below paragraph stays below the checkbox pair, radio pair, two inputs, two links, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-break-flow-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-break-flow-probe.ps1 new file mode 100644 index 000000000..0b8e2c3d3 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-break-flow-probe.ps1 @@ -0,0 +1,143 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$port = 8140 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$outPng = Join-Path $root "break-flow.png" +$browserOut = Join-Path $root "break.browser.stdout.txt" +$browserErr = Join-Path $root "break.browser.stderr.txt" +$serverOut = Join-Path $root "break.server.stdout.txt" +$serverErr = Join-Path $root "break.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +function Count-BlackInBand($bmp, $maxX, $minY, $maxY) { + $count = 0 + for ($y = $minY; $y -le $maxY; $y++) { + for ($x = 0; $x -lt $maxX; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -le 70 -and $c.G -le 70 -and $c.B -le 70) { $count++ } + } + } + return $count +} + +$server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + $ready = $false + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/break.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "break inline probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-break" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/break.html","--window_width","760","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + $pngReady = $false + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "break inline screenshot did not become ready" } + + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $red = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $blue = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 170 -and $c.G -le 90 -and $c.B -le 90) { Add-Pixel $red $x $y } + if ($c.G -ge 120 -and $c.R -le 90 -and $c.B -le 110) { Add-Pixel $green $x $y } + if ($c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120) { Add-Pixel $blue $x $y } + } + } + if ($null -eq $red.min_y -or $null -eq $green.min_y) { throw "break inline probe did not find the red and green chips" } + + $firstRowBlack = Count-BlackInBand $bmp $red.min_x ([Math]::Max(0, $red.min_y - 8)) ([Math]::Min($bmp.Height - 1, $red.max_y + 4)) + $secondRowBlack = Count-BlackInBand $bmp $green.min_x ([Math]::Max(0, $green.min_y - 8)) ([Math]::Min($bmp.Height - 1, $green.max_y + 4)) + $belowBlack = Count-BlackInBand $bmp $bmp.Width ([Math]::Min($bmp.Height - 1, $green.max_y + 22)) ([Math]::Min($bmp.Height - 1, $green.max_y + 54)) + + $breakWorked = $green.min_y -gt ($red.min_y + 24) + $firstRowWorked = $firstRowBlack -ge 20 + $secondRowWorked = $secondRowBlack -ge 20 + $belowWorked = $belowBlack -ge 20 + if (-not $breakWorked) { throw "break inline probe did not observe a lower row after br" } + if (-not $firstRowWorked) { throw "break inline probe did not observe direct text on the first row" } + if (-not $secondRowWorked) { throw "break inline probe did not observe direct text on the second row" } + if (-not $belowWorked) { throw "break inline probe did not observe the following paragraph below the break content" } + + [ordered]@{ + ready = $true + red = $red + green = $green + break_worked = $breakWorked + first_row_black = $firstRowBlack + second_row_black = $secondRowBlack + below_black = $belowBlack + } | ConvertTo-Json -Depth 5 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-break-input-enter-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-break-input-enter-submit-probe.ps1 new file mode 100644 index 000000000..5b17ea26e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-break-input-enter-submit-probe.ps1 @@ -0,0 +1,185 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8148 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "break-input-submit.png" +$browserOut = Join-Path $root "break-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "break-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "break-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "break-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$input = $null +$titleBefore = $null +$titleAfterType = $null +$titleAfterEnter = $null +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/input-break-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline break input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-break-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/input-break-submit.html","--window_width","820","--window_height","520","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline break input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline break input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $input = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 170 -and $c.G -le 195 -and $c.B -ge 195 -and $c.B -le 220) { + Add-Pixel $input $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $input.min_y) { throw "inline break input submit probe did not isolate the input control" } + + $clickClientX = [int][Math]::Floor(($input.min_x + $input.max_x) / 2) + $clickClientY = [int][Math]::Floor(($input.min_y + $input.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + + $titleAfterType = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterType = Get-SmokeWindowTitle $hwnd + if ($titleAfterType -like "Inline Input QZ*") { break } + } + if ($titleAfterType -notlike "Inline Input QZ*") { throw "inline break input did not update title after typing" } + + Send-SmokeEnter + $titleAfterEnter = $titleAfterType + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterEnter = Get-SmokeWindowTitle $hwnd + if ($titleAfterEnter -like "Inline Break Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted\.html\?entry=QZ HTTP/1\.1" 200' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + input_bounds = $input + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after_type = $titleAfterType + title_after_enter = $titleAfterEnter + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-break-input-type-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-break-input-type-probe.ps1 new file mode 100644 index 000000000..b3ddc38e7 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-break-input-type-probe.ps1 @@ -0,0 +1,165 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8146 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "break-input-type.png" +$browserOut = Join-Path $root "break-input-type.browser.stdout.txt" +$browserErr = Join-Path $root "break-input-type.browser.stderr.txt" +$serverOut = Join-Path $root "break-input-type.server.stdout.txt" +$serverErr = Join-Path $root "break-input-type.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$input = $null +$titleBefore = $null +$titleAfter = $null +$typedWorked = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/input-break.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline break input probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-break-input" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/input-break.html","--window_width","820","--window_height","500","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline break input screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline break input window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $input = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 170 -and $c.G -le 195 -and $c.B -ge 195 -and $c.B -le 220) { + Add-Pixel $input $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $input.min_y) { throw "inline break input probe did not isolate the input control" } + + $clickClientX = [int][Math]::Floor(($input.min_x + $input.max_x) / 2) + $clickClientY = [int][Math]::Floor(($input.min_y + $input.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Inline Input QZ*") { + $typedWorked = $true + break + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + input_bounds = $input + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + typed_worked = $typedWorked + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-break-link-click-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-break-link-click-probe.ps1 new file mode 100644 index 000000000..c64852a36 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-break-link-click-probe.ps1 @@ -0,0 +1,183 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8143 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "break-link-click.png" +$browserOut = Join-Path $root "break-link-click.browser.stdout.txt" +$browserErr = Join-Path $root "break-link-click.browser.stderr.txt" +$serverOut = Join-Path $root "break-link-click.server.stdout.txt" +$serverErr = Join-Path $root "break-link-click.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$green = $null +$blue = $null +$titleBefore = $null +$titleAfter = $null +$navigated = $false +$serverSawNext = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/break.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "break inline link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-break-click" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/break.html","--window_width","760","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "break inline link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "break inline link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 165 -and $c.G -le 190 -and $c.B -ge 65 -and $c.B -le 90) { Add-Pixel $green $x $y } + } + } + if ($null -eq $green.min_y) { throw "break inline link probe did not find green chip" } + + $blue = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $blueBandMin = [Math]::Max(0, $green.min_y - 12) + $blueBandMax = [Math]::Min($bmp.Height - 1, $green.max_y + 24) + for ($y = $blueBandMin; $y -le $blueBandMax; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 70 -and $c.G -le 100 -and $c.B -ge 200 -and $c.B -le 225) { Add-Pixel $blue $x $y } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $blue.min_y) { throw "break inline link probe did not isolate the second-row blue fragment" } + + $clickClientX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $clickClientY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Inline Flow Target*") { + $navigated = $true + break + } + } + if (-not $navigated -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $navigated = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + green_bounds = $green + blue_bounds = $blue + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + navigated = $navigated + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-button-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-button-link-probe.ps1 new file mode 100644 index 000000000..6e74bfa64 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-button-link-probe.ps1 @@ -0,0 +1,213 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-button-link.png" +$browserOut = Join-Path $root "checkbox-button-link.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-button-link.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-button-link.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-button-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkbox = $null +$button = $null +$titleBefore = $null +$titleAfterCheckbox = $null +$titleAfterButton = $null +$titleAfterLink = $null +$checkboxWorked = $false +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxClickPoint = $null +$buttonClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-button-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox button link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-button-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-button-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox button link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox button link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkbox = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 200 -and $c.R -le 225 -and $c.G -ge 125 -and $c.G -le 150 -and $c.B -ge 15 -and $c.B -le 40) { + Add-Pixel $checkbox $x $y + } elseif ($c.R -ge 180 -and $c.R -le 210 -and $c.G -ge 40 -and $c.G -le 85 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkbox.min_y) { throw "inline checkbox button link probe did not isolate the checkbox control" } + if ($null -eq $button.min_y) { throw "inline checkbox button link probe did not isolate the button control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $centerX = [int][Math]::Floor(($checkbox.min_x + $checkbox.max_x) / 2) + $centerY = [int][Math]::Floor(($checkbox.min_y + $checkbox.max_y) / 2) + $checkboxClickPoint = Invoke-SmokeClientClick $hwnd $centerX $centerY + $titleAfterCheckbox = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckbox = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckbox -like "Dense Checkbox True*") { + $checkboxWorked = $true + break + } + } + if (-not $checkboxWorked) { throw "inline checkbox button link probe checkbox did not toggle true on click" } + + $buttonCenterX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $buttonCenterY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + $buttonClickPoint = Invoke-SmokeClientClick $hwnd $buttonCenterX $buttonCenterY + $titleAfterButton = $titleAfterCheckbox + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Checkbox Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "inline checkbox button link probe button did not activate on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_bounds = $checkbox + button_bounds = $button + title_before = $titleBefore + title_after_checkbox = $titleAfterCheckbox + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + checkbox_click_screen = $checkboxClickPoint + button_click_screen = $buttonClickPoint + checkbox_worked = $checkboxWorked + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-link-probe.ps1 new file mode 100644 index 000000000..6fd2f0395 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-link-probe.ps1 @@ -0,0 +1,203 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8152 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-link.png" +$browserOut = Join-Path $root "checkbox-link.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-link.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-link.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkbox = $null +$titleBefore = $null +$titleAfterClick = $null +$titleAfterSpace = $null +$titleAfterLink = $null +$clickWorked = $false +$spaceWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "checkbox-link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "checkbox-link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "checkbox-link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkbox = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 200 -and $c.R -le 225 -and $c.G -ge 125 -and $c.G -le 150 -and $c.B -ge 15 -and $c.B -le 40) { + Add-Pixel $checkbox $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkbox.min_y) { throw "checkbox-link probe did not isolate the checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $centerX = [int][Math]::Floor(($checkbox.min_x + $checkbox.max_x) / 2) + $centerY = [int][Math]::Floor(($checkbox.min_y + $checkbox.max_y) / 2) + $clickPoint = Invoke-SmokeClientClick $hwnd $centerX $centerY + $titleAfterClick = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterClick = Get-SmokeWindowTitle $hwnd + if ($titleAfterClick -like "Inline Checkbox True*") { + $clickWorked = $true + break + } + } + if (-not $clickWorked) { throw "checkbox-link probe checkbox did not toggle true on click" } + + Send-SmokeSpace + $titleAfterSpace = $titleAfterClick + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterSpace = Get-SmokeWindowTitle $hwnd + if ($titleAfterSpace -like "Inline Checkbox False*") { + $spaceWorked = $true + break + } + } + if (-not $spaceWorked) { throw "checkbox-link probe checkbox did not toggle false on space" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterSpace + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_bounds = $checkbox + title_before = $titleBefore + title_after_click = $titleAfterClick + title_after_space = $titleAfterSpace + title_after_link = $titleAfterLink + click_screen = $clickPoint + click_worked = $clickWorked + space_worked = $spaceWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-button-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-button-link-probe.ps1 new file mode 100644 index 000000000..36aab36b1 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-button-link-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-pair-button-link.png" +$browserOut = Join-Path $root "checkbox-pair-button-link.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-pair-button-link.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-pair-button-link.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-pair-button-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterButton = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-pair-button-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox pair button link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-pair-button-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-pair-button-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox pair button link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox pair button link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox pair button link probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Checkbox Pair one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox pair button link probe first checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Checkbox Pair two true*") { + $checkboxTwoWorked = $true + break + } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox pair button link probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterButton = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Checkbox Pair Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "inline checkbox pair button link probe button did not activate on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-input-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-input-submit-probe.ps1 new file mode 100644 index 000000000..e9c9d3e76 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-input-submit-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-pair-input-submit.png" +$browserOut = Join-Path $root "checkbox-pair-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-pair-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-pair-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-pair-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterInput = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$inputWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-pair-input-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox pair input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-pair-input-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-pair-input-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox pair input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox pair input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox pair input submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Checkbox Input one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox pair input submit probe first checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Checkbox Input two true*") { + $checkboxTwoWorked = $true + break + } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox pair input submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInput = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInput = Get-SmokeWindowTitle $hwnd + if ($titleAfterInput -like "Dense Checkbox Input entry QZ*") { + $inputWorked = $true + break + } + } + if (-not $inputWorked) { throw "inline checkbox pair input submit probe input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInput + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Input Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-input\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_input = $titleAfterInput + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + input_worked = $inputWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-submit-probe.ps1 new file mode 100644 index 000000000..d71baebc9 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-pair-submit-probe.ps1 @@ -0,0 +1,205 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-pair-submit.png" +$browserOut = Join-Path $root "checkbox-pair-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-pair-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-pair-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-pair-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-pair-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox pair submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-pair-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-pair-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox pair submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox pair submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox pair submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Checkbox Submit one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox pair submit probe first checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Checkbox Submit two true*") { + $checkboxTwoWorked = $true + break + } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox pair submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Pair Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-pair\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-button-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-button-link-probe.ps1 new file mode 100644 index 000000000..33352c45e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-button-link-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-button-link.png" +$browserOut = Join-Path $root "checkbox-radio-button-link.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-button-link.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-button-link.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-button-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkbox = $null +$titleBefore = $null +$titleAfterCheckbox = $null +$titleAfterRadio = $null +$titleAfterButton = $null +$titleAfterLink = $null +$checkboxWorked = $false +$radioWorked = $false +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-button-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio button link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-button-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-button-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio button link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio button link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkbox = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkbox $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkbox.min_y) { throw "inline checkbox radio button link probe did not isolate the checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxCenterX = [int][Math]::Floor(($checkbox.min_x + $checkbox.max_x) / 2) + $checkboxCenterY = [int][Math]::Floor(($checkbox.min_y + $checkbox.max_y) / 2) + $checkboxClickPoint = Invoke-SmokeClientClick $hwnd $checkboxCenterX $checkboxCenterY + $titleAfterCheckbox = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckbox = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckbox -like "Dense Combo Checkbox True*") { + $checkboxWorked = $true + break + } + } + if (-not $checkboxWorked) { throw "inline checkbox radio button link probe checkbox did not toggle true on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadio = $titleAfterCheckbox + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadio = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadio -like "Dense Combo Radio two true*") { + $radioWorked = $true + break + } + } + if (-not $radioWorked) { throw "inline checkbox radio button link probe radio did not activate on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterButton = $titleAfterRadio + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Combo Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "inline checkbox radio button link probe button did not activate on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_bounds = $checkbox + title_before = $titleBefore + title_after_checkbox = $titleAfterCheckbox + title_after_radio = $titleAfterRadio + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + checkbox_click_screen = $checkboxClickPoint + checkbox_worked = $checkboxWorked + radio_worked = $radioWorked + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-input-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-input-submit-probe.ps1 new file mode 100644 index 000000000..2d0627e1d --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-input-submit-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-input-submit.png" +$browserOut = Join-Path $root "checkbox-radio-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkbox = $null +$titleBefore = $null +$titleAfterCheckbox = $null +$titleAfterRadio = $null +$titleAfterInput = $null +$titleAfterSubmit = $null +$checkboxWorked = $false +$radioWorked = $false +$inputWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-input-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-input-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-input-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkbox = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkbox $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkbox.min_y) { throw "inline checkbox radio input submit probe did not isolate the checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxCenterX = [int][Math]::Floor(($checkbox.min_x + $checkbox.max_x) / 2) + $checkboxCenterY = [int][Math]::Floor(($checkbox.min_y + $checkbox.max_y) / 2) + $checkboxClickPoint = Invoke-SmokeClientClick $hwnd $checkboxCenterX $checkboxCenterY + $titleAfterCheckbox = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckbox = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckbox -like "Dense Combo Input checkbox true*") { + $checkboxWorked = $true + break + } + } + if (-not $checkboxWorked) { throw "inline checkbox radio input submit probe checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadio = $titleAfterCheckbox + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadio = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadio -like "Dense Combo Input radio true*") { + $radioWorked = $true + break + } + } + if (-not $radioWorked) { throw "inline checkbox radio input submit probe radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInput = $titleAfterRadio + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInput = Get-SmokeWindowTitle $hwnd + if ($titleAfterInput -like "Dense Combo Input entry QZ*") { + $inputWorked = $true + break + } + } + if (-not $inputWorked) { throw "inline checkbox radio input submit probe input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInput + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Input Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-input\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_bounds = $checkbox + title_before = $titleBefore + title_after_checkbox = $titleAfterCheckbox + title_after_radio = $titleAfterRadio + title_after_input = $titleAfterInput + title_after_submit = $titleAfterSubmit + checkbox_click_screen = $checkboxClickPoint + checkbox_worked = $checkboxWorked + radio_worked = $radioWorked + input_worked = $inputWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-input-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-input-submit-probe.ps1 new file mode 100644 index 000000000..b03dc63f0 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-input-submit-probe.ps1 @@ -0,0 +1,259 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-input-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInput = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-input-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-input-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-input-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair input submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Multi checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair input submit probe first checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Multi checkbox two true*") { + $checkboxTwoWorked = $true + break + } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair input submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Multi radio one true*") { + $radioOneWorked = $true + break + } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair input submit probe first radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Multi radio two true*") { + $radioTwoWorked = $true + break + } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair input submit probe second radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInput = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInput = Get-SmokeWindowTitle $hwnd + if ($titleAfterInput -like "Dense Multi entry QZ*") { + $inputWorked = $true + break + } + } + if (-not $inputWorked) { throw "inline checkbox radio pair input submit probe input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInput + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Input Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-input\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input = $titleAfterInput + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_worked = $inputWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-eight-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-eight-probe.ps1 new file mode 100644 index 000000000..588903cd8 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-eight-probe.ps1 @@ -0,0 +1,261 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-eight.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-eight.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-eight.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-eight.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-eight.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link seventh probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-eight" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link seventh probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Eight*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-eight\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-five-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-five-probe.ps1 new file mode 100644 index 000000000..2e45f58b2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-five-probe.ps1 @@ -0,0 +1,255 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-five.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-five.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-five.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-five.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-five.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link fifth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-five" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link fifth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link fifth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link fifth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link fifth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link fifth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link fifth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link fifth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Five*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-five\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-four-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-four-probe.ps1 new file mode 100644 index 000000000..23d7f1c08 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-four-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-four.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-four.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-four.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-four.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-four.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-four" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link fourth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Four*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-four\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-one-probe.ps1 new file mode 100644 index 000000000..2ef8b1a19 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-seven-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-seven-probe.ps1 new file mode 100644 index 000000000..949b7635f --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-seven-probe.ps1 @@ -0,0 +1,259 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-seven.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-seven.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-seven.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-seven.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-seven.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link seventh probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-seven" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link seventh probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link seventh probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link seventh probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Seven*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-seven\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-six-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-six-probe.ps1 new file mode 100644 index 000000000..1c23797e0 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-six-probe.ps1 @@ -0,0 +1,257 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-six.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-six.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-six.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-six.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-six.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link sixth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-six" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link sixth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link sixth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link sixth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link sixth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link sixth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link sixth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link sixth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Six*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-six\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-submit-probe.ps1 new file mode 100644 index 000000000..854323d63 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-submit-probe.ps1 @@ -0,0 +1,261 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Eight Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-eight-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input eight-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-three-probe.ps1 new file mode 100644 index 000000000..f145a0418 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-two-probe.ps1 new file mode 100644 index 000000000..3100d91cb --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-eight-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-eight-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-eight-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-eight-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input eight-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-eight-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-eight-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input eight-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input eight-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input eight-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Eight Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input eight-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Eight Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input eight-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Eight Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input eight-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Eight Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input eight-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Eight Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input eight-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Eight Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input eight-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-five-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-five-probe.ps1 new file mode 100644 index 000000000..6f439b28f --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-five-probe.ps1 @@ -0,0 +1,255 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-five.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-five.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-five.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-five.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-five.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link fifth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-five" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link fifth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link fifth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link fifth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link fifth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link fifth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link fifth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link fifth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Five*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-five\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-four-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-four-probe.ps1 new file mode 100644 index 000000000..af0ae1dd8 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-four-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-four.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-four.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-four.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-four.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-four.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-four" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link fourth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Four*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-four\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-one-probe.ps1 new file mode 100644 index 000000000..0a1d1a3ac --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-submit-probe.ps1 new file mode 100644 index 000000000..45e53a792 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-submit-probe.ps1 @@ -0,0 +1,255 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Five Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-five-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input five-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-three-probe.ps1 new file mode 100644 index 000000000..d3d50fe9c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-two-probe.ps1 new file mode 100644 index 000000000..486c10223 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-five-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-five-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-five-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-five-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input five-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-five-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-five-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input five-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input five-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input five-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Five Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input five-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Five Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input five-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Five Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input five-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Five Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input five-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Five Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input five-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Five Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input five-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-four-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-four-probe.ps1 new file mode 100644 index 000000000..45f8fd7b0 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-four-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-four-link-four.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-four.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-four.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-four.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-four.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input four-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-four-link-four" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input four-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input four-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input four-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Four Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input four-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Four Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input four-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Four Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input four-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Four Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input four-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Four Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input four-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Four Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input four-link fourth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Four*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-four\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-one-probe.ps1 new file mode 100644 index 000000000..4ec467cc6 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-four-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input four-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-four-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input four-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input four-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input four-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Four Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input four-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Four Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input four-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Four Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input four-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Four Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input four-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Four Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input four-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Four Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input four-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-submit-probe.ps1 new file mode 100644 index 000000000..3d452638f --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-submit-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-four-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input four-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-four-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input four-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input four-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input four-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Four Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input four-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Four Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input four-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Four Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input four-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Four Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input four-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Four Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input four-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Four Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input four-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Four Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-four-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input four-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-three-probe.ps1 new file mode 100644 index 000000000..ef73ca7b6 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-four-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input four-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-four-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input four-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input four-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input four-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Four Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input four-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Four Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input four-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Four Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input four-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Four Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input four-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Four Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input four-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Four Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input four-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-two-probe.ps1 new file mode 100644 index 000000000..1447cddf2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-four-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-four-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-four-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-four-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input four-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-four-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-four-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input four-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input four-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input four-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Four Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input four-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Four Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input four-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Four Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input four-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Four Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input four-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Four Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input four-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Four Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input four-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-probe.ps1 new file mode 100644 index 000000000..11ced592c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input first-link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input first-link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input first-link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input first-link probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Two Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input first-link probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Two Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input first-link probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Two Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input first-link probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Two Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input first-link probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Two Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input first-link probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Two Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input first-link probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-two-probe.ps1 new file mode 100644 index 000000000..26f0e79c2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input second-link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input second-link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input second-link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input second-link probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Two Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input second-link probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Two Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input second-link probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Two Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input second-link probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Two Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input second-link probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Two Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input second-link probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Two Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input second-link probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-five-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-five-probe.ps1 new file mode 100644 index 000000000..fb07110ed --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-five-probe.ps1 @@ -0,0 +1,255 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-five.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-five.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-five.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-five.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-five.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link fifth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-five" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link fifth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link fifth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link fifth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link fifth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link fifth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link fifth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link fifth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Five*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-five\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-four-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-four-probe.ps1 new file mode 100644 index 000000000..9944455ec --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-four-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-four.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-four.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-four.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-four.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-four.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-four" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link fourth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Four*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-four\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-one-probe.ps1 new file mode 100644 index 000000000..654d99928 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-seven-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-seven-probe.ps1 new file mode 100644 index 000000000..7cf155004 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-seven-probe.ps1 @@ -0,0 +1,259 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-seven.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-seven.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-seven.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-seven.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-seven.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link seventh probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-seven" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link seventh probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link seventh probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link seventh probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link seventh probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link seventh probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link seventh probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link seventh probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Seven*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-seven\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-six-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-six-probe.ps1 new file mode 100644 index 000000000..f1caa31ad --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-six-probe.ps1 @@ -0,0 +1,257 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-six.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-six.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-six.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-six.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-six.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link sixth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-six" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link sixth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link sixth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link sixth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link sixth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link sixth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link sixth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link sixth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Six*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-six\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-submit-probe.ps1 new file mode 100644 index 000000000..c909686b4 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-submit-probe.ps1 @@ -0,0 +1,259 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Seven Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-seven-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input seven-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-three-probe.ps1 new file mode 100644 index 000000000..f5b14e083 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-two-probe.ps1 new file mode 100644 index 000000000..757c99e1e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-seven-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-seven-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-seven-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-seven-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input seven-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-seven-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-seven-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input seven-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input seven-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input seven-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Seven Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input seven-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Seven Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input seven-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Seven Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input seven-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Seven Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input seven-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Seven Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input seven-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Seven Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input seven-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-five-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-five-probe.ps1 new file mode 100644 index 000000000..ca622e947 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-five-probe.ps1 @@ -0,0 +1,255 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-five.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-five.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-five.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-five.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-five.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link fifth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-five" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link fifth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link fifth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link fifth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link fifth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link fifth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link fifth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link fifth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Five*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-five\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-four-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-four-probe.ps1 new file mode 100644 index 000000000..094d77e05 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-four-probe.ps1 @@ -0,0 +1,253 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-four.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-four.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-four.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-four.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-four.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-four" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link fourth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Four*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-four\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-one-probe.ps1 new file mode 100644 index 000000000..93440ea03 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-six-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-six-probe.ps1 new file mode 100644 index 000000000..946a890f7 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-six-probe.ps1 @@ -0,0 +1,257 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-six.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-six.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-six.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-six.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-six.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link sixth probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-six" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link sixth probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link sixth probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link sixth probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link sixth probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link sixth probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link sixth probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link sixth probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Six*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-six\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-submit-probe.ps1 new file mode 100644 index 000000000..f3b9e51dd --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-submit-probe.ps1 @@ -0,0 +1,257 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Six Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-six-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input six-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-three-probe.ps1 new file mode 100644 index 000000000..5f01f7d8c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-two-probe.ps1 new file mode 100644 index 000000000..8edf5b785 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-six-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-six-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-six-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-six-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input six-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-six-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-six-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input six-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input six-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input six-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Six Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input six-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Six Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input six-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Six Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input six-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Six Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input six-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Six Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input six-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Six Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input six-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-link-submit-probe.ps1 new file mode 100644 index 000000000..0c1416121 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-link-submit-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-two-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-two-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-two-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-two-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-two-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input two-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-two-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-two-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input two-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input two-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input two-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Two Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input two-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Two Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input two-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Two Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input two-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Two Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input two-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Two Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input two-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Two Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input two-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Two Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-two-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input two-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-probe.ps1 new file mode 100644 index 000000000..c490c8edd --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-submit-probe.ps1 @@ -0,0 +1,277 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Multi Two checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input submit probe first checkbox did not toggle on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Multi Two checkbox two true*") { + $checkboxTwoWorked = $true + break + } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Multi Two radio one true*") { + $radioOneWorked = $true + break + } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input submit probe first radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Multi Two radio two true*") { + $radioTwoWorked = $true + break + } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input submit probe second radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Multi Two entry one QZ*") { + $inputOneWorked = $true + break + } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input submit probe first input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Multi Two entry two LM*") { + $inputTwoWorked = $true + break + } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-one-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-one-probe.ps1 new file mode 100644 index 000000000..3abd3aeae --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-one-probe.ps1 @@ -0,0 +1,247 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-three-link-one.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-one.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-one.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-one.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-one.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input three-link first probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-three-link-one" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input three-link first screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input three-link first window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input three-link first probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Three Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input three-link first probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Three Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input three-link first probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Three Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input three-link first probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Three Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input three-link first probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Three Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input three-link first probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Three Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input three-link first probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-submit-probe.ps1 new file mode 100644 index 000000000..3689eac63 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-submit-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-three-link-submit.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-submit.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-submit.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-submit.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterSubmit = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input three-link submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-three-link-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input three-link submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input three-link submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input three-link submit probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Three Link checkbox one true*") { $checkboxOneWorked = $true; break } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input three-link submit probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Three Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input three-link submit probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Three Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input three-link submit probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Three Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input three-link submit probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Three Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input three-link submit probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Three Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input three-link submit probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Checkbox Radio Pair Two Input Three Link Submitted*") { $submitWorked = $true; break } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-checkbox-radio-pair-two-input-three-link\.html(\?| )' + if ($serverSawSubmit) { $submitWorked = $true } + } + if (-not $submitWorked) { throw "inline checkbox radio pair two input three-link submit probe did not reach the submitted page" } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_submit = $titleAfterSubmit + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-three-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-three-probe.ps1 new file mode 100644 index 000000000..cc4b32044 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-three-probe.ps1 @@ -0,0 +1,251 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-three-link-three.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-three.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-three.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-three.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-three.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input three-link third probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-three-link-three" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input three-link third screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input three-link third window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input three-link third probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Three Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input three-link third probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Three Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input three-link third probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Three Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input three-link third probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Three Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input three-link third probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Three Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input three-link third probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Three Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input three-link third probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Three*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-three\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-two-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-two-probe.ps1 new file mode 100644 index 000000000..3a91421e1 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-checkbox-radio-pair-two-input-three-link-two-probe.ps1 @@ -0,0 +1,249 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "checkbox-radio-pair-two-input-three-link-two.png" +$browserOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-two.browser.stdout.txt" +$browserErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-two.browser.stderr.txt" +$serverOut = Join-Path $root "checkbox-radio-pair-two-input-three-link-two.server.stdout.txt" +$serverErr = Join-Path $root "checkbox-radio-pair-two-input-three-link-two.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$checkboxOne = $null +$titleBefore = $null +$titleAfterCheckboxOne = $null +$titleAfterCheckboxTwo = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInputOne = $null +$titleAfterInputTwo = $null +$titleAfterLink = $null +$checkboxOneWorked = $false +$checkboxTwoWorked = $false +$radioOneWorked = $false +$radioTwoWorked = $false +$inputOneWorked = $false +$inputTwoWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$checkboxOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline checkbox radio pair two input three-link second probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-checkbox-radio-pair-two-input-three-link-two" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/checkbox-radio-pair-two-input-three-link-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline checkbox radio pair two input three-link second screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline checkbox radio pair two input three-link second window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $checkboxOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $checkboxOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $checkboxOne.min_y) { throw "inline checkbox radio pair two input three-link second probe did not isolate the first checkbox control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $checkboxOneCenterX = [int][Math]::Floor(($checkboxOne.min_x + $checkboxOne.max_x) / 2) + $checkboxOneCenterY = [int][Math]::Floor(($checkboxOne.min_y + $checkboxOne.max_y) / 2) + $checkboxOneClickPoint = Invoke-SmokeClientClick $hwnd $checkboxOneCenterX $checkboxOneCenterY + $titleAfterCheckboxOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxOne -like "Dense Three Link checkbox one true*") { + $checkboxOneWorked = $true + break + } + } + if (-not $checkboxOneWorked) { throw "inline checkbox radio pair two input three-link second probe first checkbox did not toggle on click" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterCheckboxTwo = $titleAfterCheckboxOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterCheckboxTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterCheckboxTwo -like "Dense Three Link checkbox two true*") { $checkboxTwoWorked = $true; break } + } + if (-not $checkboxTwoWorked) { throw "inline checkbox radio pair two input three-link second probe second checkbox did not toggle on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioOne = $titleAfterCheckboxTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Three Link radio one true*") { $radioOneWorked = $true; break } + } + if (-not $radioOneWorked) { throw "inline checkbox radio pair two input three-link second probe first radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Three Link radio two true*") { $radioTwoWorked = $true; break } + } + if (-not $radioTwoWorked) { throw "inline checkbox radio pair two input three-link second probe second radio did not select on space after tab" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "QZ" + $titleAfterInputOne = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputOne -like "Dense Three Link entry one QZ*") { $inputOneWorked = $true; break } + } + if (-not $inputOneWorked) { throw "inline checkbox radio pair two input three-link second probe first input did not update after typing" } + + Send-SmokeTab; Start-Sleep -Milliseconds 120; Send-SmokeText "LM" + $titleAfterInputTwo = $titleAfterInputOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInputTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterInputTwo -like "Dense Three Link entry two LM*") { $inputTwoWorked = $true; break } + } + if (-not $inputTwoWorked) { throw "inline checkbox radio pair two input three-link second probe second input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInputTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target Two*") { $linkWorked = $true; break } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next-two\.html(\?| )' + if ($serverSawNext) { $linkWorked = $true } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + checkbox_one_bounds = $checkboxOne + title_before = $titleBefore + title_after_checkbox_one = $titleAfterCheckboxOne + title_after_checkbox_two = $titleAfterCheckboxTwo + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input_one = $titleAfterInputOne + title_after_input_two = $titleAfterInputTwo + title_after_link = $titleAfterLink + checkbox_one_click_screen = $checkboxOneClickPoint + checkbox_one_worked = $checkboxOneWorked + checkbox_two_worked = $checkboxTwoWorked + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_one_worked = $inputOneWorked + input_two_worked = $inputTwoWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-control-link-click-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-control-link-click-probe.ps1 new file mode 100644 index 000000000..5a5a65ac1 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-control-link-click-probe.ps1 @@ -0,0 +1,197 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8149 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "control-link-click.png" +$browserOut = Join-Path $root "control-link-click.browser.stdout.txt" +$browserErr = Join-Path $root "control-link-click.browser.stderr.txt" +$serverOut = Join-Path $root "control-link-click.server.stdout.txt" +$serverErr = Join-Path $root "control-link-click.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$button = $null +$link = $null +$titleBefore = $null +$titleAfterButton = $null +$titleAfterLink = $null +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$buttonClickPoint = $null +$linkClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/control-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "control-link coexistence probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-control-link-click" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/control-link.html","--window_width","720","--window_height","540","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "control-link coexistence screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "control-link coexistence window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $link = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 175 -and $c.R -le 210 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 70 -and $c.G -le 100 -and $c.B -ge 200 -and $c.B -le 225) { + Add-Pixel $link $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $button.min_y) { throw "control-link coexistence probe did not isolate the button fragment" } + if ($null -eq $link.min_y) { throw "control-link coexistence probe did not isolate the link fragment" } + if ($link.min_y -le $button.min_y + 12) { throw "control-link coexistence probe did not place the link on a later row" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $buttonCenterX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $buttonCenterY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + $buttonClickPoint = Invoke-SmokeClientClick $hwnd $buttonCenterX $buttonCenterY + $titleAfterButton = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Inline Control Link Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "control-link coexistence probe button activation did not work" } + + $linkCenterX = [int][Math]::Floor(($link.min_x + $link.max_x) / 2) + $linkCenterY = [int][Math]::Floor(($link.min_y + $link.max_y) / 2) + $linkClickPoint = Invoke-SmokeClientClick $hwnd $linkCenterX $linkCenterY + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + button_bounds = $button + link_bounds = $link + title_before = $titleBefore + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + button_click = $buttonClickPoint + link_click = $linkClickPoint + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-control-link-tab-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-control-link-tab-probe.ps1 new file mode 100644 index 000000000..d3dfaebf5 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-control-link-tab-probe.ps1 @@ -0,0 +1,188 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8150 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "control-link-tab.png" +$browserOut = Join-Path $root "control-link-tab.browser.stdout.txt" +$browserErr = Join-Path $root "control-link-tab.browser.stderr.txt" +$serverOut = Join-Path $root "control-link-tab.server.stdout.txt" +$serverErr = Join-Path $root "control-link-tab.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$button = $null +$titleBefore = $null +$titleAfterButton = $null +$titleAfterEnter = $null +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/control-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "control-link tab probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-control-link-tab" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/control-link.html","--window_width","720","--window_height","540","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "control-link tab probe screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "control-link tab probe window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 175 -and $c.R -le 210 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $button.min_y) { throw "control-link tab probe did not isolate the button fragment" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $buttonCenterX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $buttonCenterY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + $clickPoint = Invoke-SmokeClientClick $hwnd $buttonCenterX $buttonCenterY + $titleAfterButton = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Inline Control Link Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "control-link tab probe button activation did not work" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + + $titleAfterEnter = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfterEnter = Get-SmokeWindowTitle $hwnd + if ($titleAfterEnter -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + button_bounds = $button + title_before = $titleBefore + title_after_button = $titleAfterButton + title_after_enter = $titleAfterEnter + click_screen = $clickPoint + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-dense-focus-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-dense-focus-probe.ps1 new file mode 100644 index 000000000..0cbcf75ab --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-dense-focus-probe.ps1 @@ -0,0 +1,205 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8151 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "dense-focus.png" +$browserOut = Join-Path $root "dense-focus.browser.stdout.txt" +$browserErr = Join-Path $root "dense-focus.browser.stderr.txt" +$serverOut = Join-Path $root "dense-focus.server.stdout.txt" +$serverErr = Join-Path $root "dense-focus.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$button = $null +$titleBefore = $null +$titleAfterButton = $null +$titleAfterInput = $null +$titleAfterLink = $null +$buttonWorked = $false +$inputWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$buttonClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/dense-focus.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "dense inline focus probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-dense-focus" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/dense-focus.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "dense inline focus screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "dense inline focus window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 175 -and $c.R -le 210 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $button.min_y) { throw "dense inline focus probe did not isolate the button fragment" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $buttonCenterX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $buttonCenterY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + $buttonClickPoint = Invoke-SmokeClientClick $hwnd $buttonCenterX $buttonCenterY + $titleAfterButton = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "dense inline focus probe button activation did not work" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInput = $titleAfterButton + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInput = Get-SmokeWindowTitle $hwnd + if ($titleAfterInput -like "Dense Input QZ*") { + $inputWorked = $true + break + } + } + if (-not $inputWorked) { throw "dense inline focus probe input typing did not work after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterInput + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + button_bounds = $button + title_before = $titleBefore + title_after_button = $titleAfterButton + title_after_input = $titleAfterInput + title_after_link = $titleAfterLink + button_click = $buttonClickPoint + button_worked = $buttonWorked + input_worked = $inputWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-long-wrap-link-click-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-long-wrap-link-click-probe.ps1 new file mode 100644 index 000000000..0f84bd7bb --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-long-wrap-link-click-probe.ps1 @@ -0,0 +1,184 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8144 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "wrapped-long-link-click.png" +$browserOut = Join-Path $root "wrapped-long-link-click.browser.stdout.txt" +$browserErr = Join-Path $root "wrapped-long-link-click.browser.stderr.txt" +$serverOut = Join-Path $root "wrapped-long-link-click.server.stdout.txt" +$serverErr = Join-Path $root "wrapped-long-link-click.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$top = $null +$lower = $null +$titleBefore = $null +$titleAfter = $null +$navigated = $false +$serverSawNext = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/wrapped-long.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped long inline link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-wrap-long-click" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/wrapped-long.html","--window_width","700","--window_height","520","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped long inline link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "wrapped long inline link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $top = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 15 -and $c.R -le 45 -and $c.G -ge 70 -and $c.G -le 100 -and $c.B -ge 200 -and $c.B -le 225) { Add-Pixel $top $x $y } + } + } + if ($null -eq $top.min_y) { throw "wrapped long inline link probe did not find the top blue fragment" } + + $lower = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $purpleBandMin = [Math]::Min($bmp.Height - 1, $top.max_y + 24) + $purpleBandMax = [Math]::Min($bmp.Height - 1, $top.max_y + 140) + for ($y = $purpleBandMin; $y -le $purpleBandMax; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 105 -and $c.R -le 140 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 205 -and $c.B -le 225) { Add-Pixel $lower $x $y } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $lower.min_y) { throw "wrapped long inline link probe did not isolate the lower purple fragment" } + if ($lower.min_y -le $top.min_y + 24) { throw "wrapped long inline link probe did not observe a later lower fragment" } + + $clickClientX = [int][Math]::Floor(($lower.min_x + $lower.max_x) / 2) + $clickClientY = [int][Math]::Floor(($lower.min_y + $lower.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Inline Flow Target*") { + $navigated = $true + break + } + } + if (-not $navigated -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $navigated = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + top_bounds = $top + lower_bounds = $lower + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + navigated = $navigated + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-mixed-flow-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-mixed-flow-probe.ps1 new file mode 100644 index 000000000..d95bf1e3e --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-mixed-flow-probe.ps1 @@ -0,0 +1,120 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$port = 8138 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "inline-flow.png" +$browserOut = Join-Path $root "browser.stdout.txt" +$browserErr = Join-Path $root "browser.stderr.txt" +$serverOut = Join-Path $root "server.stdout.txt" +$serverErr = Join-Path $root "server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +$server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + $ready = $false + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "localhost probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + $pngReady = $false + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline flow screenshot did not become ready" } + + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $red = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ + } + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 170 -and $c.G -le 90 -and $c.B -le 90) { + Add-Pixel $red $x $y + } + } + } + if ($null -eq $red.min_y) { throw "red chip not found" } + + $sameLineBlack = 0 + $aboveBlack = 0 + $bandMin = [Math]::Max(0, $red.min_y - 8) + $bandMax = [Math]::Min($bmp.Height - 1, $red.max_y + 4) + $aboveMin = [Math]::Max(0, $red.min_y - 28) + $aboveMax = [Math]::Max(0, $red.min_y - 8) + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $red.min_x; $x++) { + $c = $bmp.GetPixel($x, $y) + $isBlack = ($c.R -le 70 -and $c.G -le 70 -and $c.B -le 70) + if (-not $isBlack) { continue } + if ($y -ge $bandMin -and $y -le $bandMax) { $sameLineBlack++ } + if ($y -ge $aboveMin -and $y -le $aboveMax) { $aboveBlack++ } + } + } + + $sameLineWorked = $sameLineBlack -ge 20 + $noDuplicateAbove = $aboveBlack -le 10 + if (-not $sameLineWorked) { throw "mixed inline probe did not observe left-side text on the chip row" } + if (-not $noDuplicateAbove) { throw "mixed inline probe still observed a duplicate text band above the chips" } + + [ordered]@{ + ready = $true + red = $red + same_line_black = $sameLineBlack + above_black = $aboveBlack + same_line_worked = $sameLineWorked + no_duplicate_above = $noDuplicateAbove + } | ConvertTo-Json -Depth 5 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-radio-button-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-radio-button-link-probe.ps1 new file mode 100644 index 000000000..d4a2a3384 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-radio-button-link-probe.ps1 @@ -0,0 +1,213 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "radio-button-link.png" +$browserOut = Join-Path $root "radio-button-link.browser.stdout.txt" +$browserErr = Join-Path $root "radio-button-link.browser.stderr.txt" +$serverOut = Join-Path $root "radio-button-link.server.stdout.txt" +$serverErr = Join-Path $root "radio-button-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$radio = $null +$button = $null +$titleBefore = $null +$titleAfterRadio = $null +$titleAfterButton = $null +$titleAfterLink = $null +$radioWorked = $false +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$radioClickPoint = $null +$buttonClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/radio-button-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline radio button link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-radio-button-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/radio-button-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline radio button link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline radio button link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $radio = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 200 -and $c.R -le 225 -and $c.G -ge 125 -and $c.G -le 150 -and $c.B -ge 15 -and $c.B -le 40) { + Add-Pixel $radio $x $y + } elseif ($c.R -ge 180 -and $c.R -le 210 -and $c.G -ge 40 -and $c.G -le 85 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $radio.min_y) { throw "inline radio button link probe did not isolate the radio control" } + if ($null -eq $button.min_y) { throw "inline radio button link probe did not isolate the button control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $radioCenterX = [int][Math]::Floor(($radio.min_x + $radio.max_x) / 2) + $radioCenterY = [int][Math]::Floor(($radio.min_y + $radio.max_y) / 2) + $radioClickPoint = Invoke-SmokeClientClick $hwnd $radioCenterX $radioCenterY + $titleAfterRadio = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadio = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadio -like "Dense Radio true*") { + $radioWorked = $true + break + } + } + if (-not $radioWorked) { throw "inline radio button link probe radio did not select on click" } + + $buttonCenterX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $buttonCenterY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + $buttonClickPoint = Invoke-SmokeClientClick $hwnd $buttonCenterX $buttonCenterY + $titleAfterButton = $titleAfterRadio + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Radio Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "inline radio button link probe button did not activate on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + radio_bounds = $radio + button_bounds = $button + title_before = $titleBefore + title_after_radio = $titleAfterRadio + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + radio_click_screen = $radioClickPoint + button_click_screen = $buttonClickPoint + radio_worked = $radioWorked + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-radio-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-radio-link-probe.ps1 new file mode 100644 index 000000000..604569fd2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-radio-link-probe.ps1 @@ -0,0 +1,187 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "radio-link.png" +$browserOut = Join-Path $root "radio-link.browser.stdout.txt" +$browserErr = Join-Path $root "radio-link.browser.stderr.txt" +$serverOut = Join-Path $root "radio-link.server.stdout.txt" +$serverErr = Join-Path $root "radio-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$radio = $null +$titleBefore = $null +$titleAfterClick = $null +$titleAfterLink = $null +$clickWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/radio-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline radio link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-radio-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/radio-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline radio link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline radio link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $radio = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 200 -and $c.R -le 225 -and $c.G -ge 125 -and $c.G -le 150 -and $c.B -ge 15 -and $c.B -le 40) { + Add-Pixel $radio $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $radio.min_y) { throw "inline radio link probe did not isolate the radio control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $centerX = [int][Math]::Floor(($radio.min_x + $radio.max_x) / 2) + $centerY = [int][Math]::Floor(($radio.min_y + $radio.max_y) / 2) + $clickPoint = Invoke-SmokeClientClick $hwnd $centerX $centerY + $titleAfterClick = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterClick = Get-SmokeWindowTitle $hwnd + if ($titleAfterClick -like "Inline Radio two True*") { + $clickWorked = $true + break + } + } + if (-not $clickWorked) { throw "inline radio link probe radio did not select on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterClick + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + radio_bounds = $radio + title_before = $titleBefore + title_after_click = $titleAfterClick + title_after_link = $titleAfterLink + click_screen = $clickPoint + click_worked = $clickWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-button-link-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-button-link-probe.ps1 new file mode 100644 index 000000000..9d59a9970 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-button-link-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "radio-pair-button-link.png" +$browserOut = Join-Path $root "radio-pair-button-link.browser.stdout.txt" +$browserErr = Join-Path $root "radio-pair-button-link.browser.stderr.txt" +$serverOut = Join-Path $root "radio-pair-button-link.server.stdout.txt" +$serverErr = Join-Path $root "radio-pair-button-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$radioOne = $null +$titleBefore = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterButton = $null +$titleAfterLink = $null +$radioOneWorked = $false +$radioTwoWorked = $false +$buttonWorked = $false +$linkWorked = $false +$serverSawNext = $false +$failure = $null +$radioOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/radio-pair-button-link.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline radio pair button link probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-radio-pair-button-link" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/radio-pair-button-link.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline radio pair button link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline radio pair button link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $radioOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $radioOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $radioOne.min_y) { throw "inline radio pair button link probe did not isolate the first radio control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $radioOneCenterX = [int][Math]::Floor(($radioOne.min_x + $radioOne.max_x) / 2) + $radioOneCenterY = [int][Math]::Floor(($radioOne.min_y + $radioOne.max_y) / 2) + $radioOneClickPoint = Invoke-SmokeClientClick $hwnd $radioOneCenterX $radioOneCenterY + $titleAfterRadioOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Pair Radio one true*") { + $radioOneWorked = $true + break + } + } + if (-not $radioOneWorked) { throw "inline radio pair button link probe first radio did not select on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Pair Radio two true*") { + $radioTwoWorked = $true + break + } + } + if (-not $radioTwoWorked) { throw "inline radio pair button link probe second radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterButton = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterButton = Get-SmokeWindowTitle $hwnd + if ($titleAfterButton -like "Dense Pair Button 1*") { + $buttonWorked = $true + break + } + } + if (-not $buttonWorked) { throw "inline radio pair button link probe button did not activate on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + $titleAfterLink = $titleAfterButton + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterLink = Get-SmokeWindowTitle $hwnd + if ($titleAfterLink -like "Inline Flow Target*") { + $linkWorked = $true + break + } + } + if (-not $linkWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $linkWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + radio_one_bounds = $radioOne + title_before = $titleBefore + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_button = $titleAfterButton + title_after_link = $titleAfterLink + radio_one_click_screen = $radioOneClickPoint + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + button_worked = $buttonWorked + link_worked = $linkWorked + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-input-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-input-submit-probe.ps1 new file mode 100644 index 000000000..b7fa7a2a6 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-input-submit-probe.ps1 @@ -0,0 +1,223 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "radio-pair-input-submit.png" +$browserOut = Join-Path $root "radio-pair-input-submit.browser.stdout.txt" +$browserErr = Join-Path $root "radio-pair-input-submit.browser.stderr.txt" +$serverOut = Join-Path $root "radio-pair-input-submit.server.stdout.txt" +$serverErr = Join-Path $root "radio-pair-input-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$radioOne = $null +$titleBefore = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterInput = $null +$titleAfterSubmit = $null +$radioOneWorked = $false +$radioTwoWorked = $false +$inputWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$radioOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/radio-pair-input-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline radio pair input submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-radio-pair-input-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/radio-pair-input-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline radio pair input submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline radio pair input submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $radioOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $radioOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $radioOne.min_y) { throw "inline radio pair input submit probe did not isolate the first radio control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $radioOneCenterX = [int][Math]::Floor(($radioOne.min_x + $radioOne.max_x) / 2) + $radioOneCenterY = [int][Math]::Floor(($radioOne.min_y + $radioOne.max_y) / 2) + $radioOneClickPoint = Invoke-SmokeClientClick $hwnd $radioOneCenterX $radioOneCenterY + $titleAfterRadioOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Radio Input one true*") { + $radioOneWorked = $true + break + } + } + if (-not $radioOneWorked) { throw "inline radio pair input submit probe first radio did not select on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Radio Input two true*") { + $radioTwoWorked = $true + break + } + } + if (-not $radioTwoWorked) { throw "inline radio pair input submit probe second radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText "QZ" + $titleAfterInput = $titleAfterRadioTwo + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterInput = Get-SmokeWindowTitle $hwnd + if ($titleAfterInput -like "Dense Radio Input entry QZ*") { + $inputWorked = $true + break + } + } + if (-not $inputWorked) { throw "inline radio pair input submit probe input did not update after typing" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterInput + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Radio Input Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-radio-input\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + radio_one_bounds = $radioOne + title_before = $titleBefore + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_input = $titleAfterInput + title_after_submit = $titleAfterSubmit + radio_one_click_screen = $radioOneClickPoint + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + input_worked = $inputWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-submit-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-submit-probe.ps1 new file mode 100644 index 000000000..4cd0ca644 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-radio-pair-submit-probe.ps1 @@ -0,0 +1,205 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "radio-pair-submit.png" +$browserOut = Join-Path $root "radio-pair-submit.browser.stdout.txt" +$browserErr = Join-Path $root "radio-pair-submit.browser.stderr.txt" +$serverOut = Join-Path $root "radio-pair-submit.server.stdout.txt" +$serverErr = Join-Path $root "radio-pair-submit.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$radioOne = $null +$titleBefore = $null +$titleAfterRadioOne = $null +$titleAfterRadioTwo = $null +$titleAfterSubmit = $null +$radioOneWorked = $false +$radioTwoWorked = $false +$submitWorked = $false +$serverSawSubmit = $false +$failure = $null +$radioOneClickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/radio-pair-submit.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "inline radio pair submit probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-radio-pair-submit" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/radio-pair-submit.html","--window_width","760","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "inline radio pair submit screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "inline radio pair submit window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $radioOne = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 25 -and $c.R -le 60 -and $c.G -ge 135 -and $c.G -le 170 -and $c.B -ge 75 -and $c.B -le 120) { + Add-Pixel $radioOne $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $radioOne.min_y) { throw "inline radio pair submit probe did not isolate the first radio control" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $radioOneCenterX = [int][Math]::Floor(($radioOne.min_x + $radioOne.max_x) / 2) + $radioOneCenterY = [int][Math]::Floor(($radioOne.min_y + $radioOne.max_y) / 2) + $radioOneClickPoint = Invoke-SmokeClientClick $hwnd $radioOneCenterX $radioOneCenterY + $titleAfterRadioOne = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioOne = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioOne -like "Dense Radio Submit one true*") { + $radioOneWorked = $true + break + } + } + if (-not $radioOneWorked) { throw "inline radio pair submit probe first radio did not select on click" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterRadioTwo = $titleAfterRadioOne + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterRadioTwo = Get-SmokeWindowTitle $hwnd + if ($titleAfterRadioTwo -like "Dense Radio Submit two true*") { + $radioTwoWorked = $true + break + } + } + if (-not $radioTwoWorked) { throw "inline radio pair submit probe second radio did not select on space after tab" } + + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeSpace + $titleAfterSubmit = $titleAfterRadioTwo + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterSubmit = Get-SmokeWindowTitle $hwnd + if ($titleAfterSubmit -like "Inline Radio Pair Submitted*") { + $submitWorked = $true + break + } + } + if (-not $submitWorked -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawSubmit = $serverLog -match 'GET /submitted-radio-pair\.html(\?| )' + if ($serverSawSubmit) { + $submitWorked = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + radio_one_bounds = $radioOne + title_before = $titleBefore + title_after_radio_one = $titleAfterRadioOne + title_after_radio_two = $titleAfterRadioTwo + title_after_submit = $titleAfterSubmit + radio_one_click_screen = $radioOneClickPoint + radio_one_worked = $radioOneWorked + radio_two_worked = $radioTwoWorked + submit_worked = $submitWorked + server_saw_submit = $serverSawSubmit + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-click-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-click-probe.ps1 new file mode 100644 index 000000000..8cbfb4d91 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-click-probe.ps1 @@ -0,0 +1,163 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8145 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "wrapped-button-click.png" +$browserOut = Join-Path $root "wrapped-button-click.browser.stdout.txt" +$browserErr = Join-Path $root "wrapped-button-click.browser.stderr.txt" +$serverOut = Join-Path $root "wrapped-button-click.server.stdout.txt" +$serverErr = Join-Path $root "wrapped-button-click.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$button = $null +$titleBefore = $null +$titleAfter = $null +$activated = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/button.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped inline button probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-button-click" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/button.html","--window_width","720","--window_height","500","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped inline button screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "wrapped inline button window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 175 -and $c.R -le 210 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $button.min_y) { throw "wrapped inline button probe did not isolate the button fragment" } + + $clickClientX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $clickClientY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Inline Button Activated*") { + $activated = $true + break + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + button_bounds = $button + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + activated = $activated + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-space-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-space-probe.ps1 new file mode 100644 index 000000000..0f37f7a77 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-button-space-probe.ps1 @@ -0,0 +1,174 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8147 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "wrapped-button-space.png" +$browserOut = Join-Path $root "wrapped-button-space.browser.stdout.txt" +$browserErr = Join-Path $root "wrapped-button-space.browser.stderr.txt" +$serverOut = Join-Path $root "wrapped-button-space.server.stdout.txt" +$serverErr = Join-Path $root "wrapped-button-space.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$button = $null +$titleBefore = $null +$titleAfterClick = $null +$titleAfterSpace = $null +$spaceWorked = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/button-keyboard.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped inline button keyboard probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-button-space" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/button-keyboard.html","--window_width","720","--window_height","500","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped inline button keyboard screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "wrapped inline button keyboard window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $button = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 175 -and $c.R -le 210 -and $c.G -ge 45 -and $c.G -le 80 -and $c.B -ge 165 -and $c.B -le 200) { + Add-Pixel $button $x $y + } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $button.min_y) { throw "wrapped inline button keyboard probe did not isolate the button fragment" } + + $clickClientX = [int][Math]::Floor(($button.min_x + $button.max_x) / 2) + $clickClientY = [int][Math]::Floor(($button.min_y + $button.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfterClick = $titleBefore + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterClick = Get-SmokeWindowTitle $hwnd + if ($titleAfterClick -like "Inline Button Count 1*") { break } + } + if ($titleAfterClick -notlike "Inline Button Count 1*") { throw "wrapped inline button did not activate on click before space test" } + + Send-SmokeSpace + $titleAfterSpace = $titleAfterClick + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 150 + $titleAfterSpace = Get-SmokeWindowTitle $hwnd + if ($titleAfterSpace -like "Inline Button Count 2*") { + $spaceWorked = $true + break + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + button_bounds = $button + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after_click = $titleAfterClick + title_after_space = $titleAfterSpace + space_worked = $spaceWorked + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-wrap-flow-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-flow-probe.ps1 new file mode 100644 index 000000000..c63143e84 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-flow-probe.ps1 @@ -0,0 +1,144 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$port = 8139 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$outPng = Join-Path $root "wrapped-flow.png" +$browserOut = Join-Path $root "wrapped.browser.stdout.txt" +$browserErr = Join-Path $root "wrapped.browser.stderr.txt" +$serverOut = Join-Path $root "wrapped.server.stdout.txt" +$serverErr = Join-Path $root "wrapped.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +function Count-BlackInBand($bmp, $maxX, $minY, $maxY) { + $count = 0 + for ($y = $minY; $y -le $maxY; $y++) { + for ($x = 0; $x -lt $maxX; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -le 70 -and $c.G -le 70 -and $c.B -le 70) { $count++ } + } + } + return $count +} + +$server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + $ready = $false + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/wrapped.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped inline probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-wrap" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/wrapped.html","--window_width","720","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + $pngReady = $false + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped inline screenshot did not become ready" } + + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $red = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $blue = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 170 -and $c.G -le 90 -and $c.B -le 90) { Add-Pixel $red $x $y } + if ($c.G -ge 120 -and $c.R -le 90 -and $c.B -le 110) { Add-Pixel $green $x $y } + if ($c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120) { Add-Pixel $blue $x $y } + } + } + if ($null -eq $red.min_y -or $null -eq $green.min_y) { throw "wrapped inline probe did not find the red and green chips" } + + $later = $green + $firstRowBlack = Count-BlackInBand $bmp $red.min_x ([Math]::Max(0, $red.min_y - 8)) ([Math]::Min($bmp.Height - 1, $red.max_y + 4)) + $laterRowBlack = Count-BlackInBand $bmp $later.min_x ([Math]::Max(0, $later.min_y - 8)) ([Math]::Min($bmp.Height - 1, $later.max_y + 4)) + $belowBlack = Count-BlackInBand $bmp $bmp.Width ([Math]::Min($bmp.Height - 1, $later.max_y + 96)) ([Math]::Min($bmp.Height - 1, $later.max_y + 170)) + + $wrapped = $later.min_y -gt ($red.min_y + 8) + $firstRowWorked = $firstRowBlack -ge 20 + $laterRowWorked = $laterRowBlack -ge 20 + $belowWorked = $belowBlack -ge 20 + if (-not $wrapped) { throw "wrapped inline probe did not observe a lower mixed row" } + if (-not $firstRowWorked) { throw "wrapped inline probe did not observe direct text on the first mixed row" } + if (-not $laterRowWorked) { throw "wrapped inline probe did not observe direct text on the lower mixed row" } + if (-not $belowWorked) { throw "wrapped inline probe did not observe the following paragraph below the wrapped content" } + + [ordered]@{ + ready = $true + red = $red + green = $green + wrapped = $wrapped + first_row_black = $firstRowBlack + later_row_black = $laterRowBlack + below_black = $belowBlack + } | ConvertTo-Json -Depth 5 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/inline-flow/chrome-inline-wrap-link-click-probe.ps1 b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-link-click-probe.ps1 new file mode 100644 index 000000000..870f465ad --- /dev/null +++ b/tmp-browser-smoke/inline-flow/chrome-inline-wrap-link-click-probe.ps1 @@ -0,0 +1,185 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8141 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "wrapped-link-click.png" +$browserOut = Join-Path $root "wrapped-link-click.browser.stdout.txt" +$browserErr = Join-Path $root "wrapped-link-click.browser.stderr.txt" +$serverOut = Join-Path $root "wrapped-link-click.server.stdout.txt" +$serverErr = Join-Path $root "wrapped-link-click.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$blue = $null +$green = $null +$titleBefore = $null +$titleAfter = $null +$navigated = $false +$serverSawNext = $false +$failure = $null +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/wrapped.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped inline click probe server did not become ready" } + + $profileRoot = Join-Path $root "profile-inline-wrap-click" + $appDataRoot = Join-Path $profileRoot "lightpanda" + cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/wrapped.html","--window_width","720","--window_height","460","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped inline click screenshot did not become ready" } + + $proc = $null + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "wrapped inline click window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.G -ge 120 -and $c.R -le 90 -and $c.B -le 110) { Add-Pixel $green $x $y } + } + } + if ($null -eq $green.min_y) { throw "wrapped inline click probe did not find the green chip" } + + $blue = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + $blueBandMin = [Math]::Min($bmp.Height - 1, $green.max_y + 8) + $blueBandMax = [Math]::Min($bmp.Height - 1, $green.max_y + 96) + for ($y = $blueBandMin; $y -le $blueBandMax; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.B -ge 170 -and $c.R -le 70 -and $c.G -le 110) { Add-Pixel $blue $x $y } + } + } + } finally { + $bmp.Dispose() + } + + if ($null -eq $blue.min_y) { throw "wrapped inline click probe did not isolate a lower blue fragment" } + if ($blue.min_y -le $green.min_y) { throw "wrapped inline click probe did not isolate a lower blue fragment" } + + $clickClientX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $clickClientY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Inline Flow Target*") { + $navigated = $true + break + } + } + if (-not $navigated -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $navigated = $true + } + } +} +catch { + $failure = $_.Exception.Message +} +finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $browser.Id } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + blue_bounds = $blue + green_bounds = $green + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickPoint) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + navigated = $navigated + server_saw_next = $serverSawNext + error = $failure + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/inline-flow/control-link.html b/tmp-browser-smoke/inline-flow/control-link.html new file mode 100644 index 000000000..1f90b3575 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/control-link.html @@ -0,0 +1,35 @@ + + + + + Mixed Inline Control Link + + + +
+

+ Lead text before button + + middle words that wrap before the link + + LINK + now + +

+

Below paragraph stays below the mixed control and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/dense-focus.html b/tmp-browser-smoke/inline-flow/dense-focus.html new file mode 100644 index 000000000..e6fd90a56 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/dense-focus.html @@ -0,0 +1,44 @@ + + + + + Dense Inline Focus Flow + + + +
+

+ Lead text before + + bridge words before the input + + trailing words before the + + LINK + now + +

+

Below paragraph stays below the dense mixed inline flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/index.html b/tmp-browser-smoke/inline-flow/index.html new file mode 100644 index 000000000..6efc97e7c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/index.html @@ -0,0 +1,20 @@ + + + + + Inline Flow Smoke + + +
+

+ Prefix text + RED + middle + GREEN + go LINK now + suffix text +

+

Inline chips should stay on one line above this paragraph.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/input-break-submit.html b/tmp-browser-smoke/inline-flow/input-break-submit.html new file mode 100644 index 000000000..b6097a0f0 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/input-break-submit.html @@ -0,0 +1,30 @@ + + + + + Inline Break Input Submit + + +
+
+

+ Prefix text before the explicit break +
+ After break + + tail text after input +

+

Below paragraph stays below the break and inline input.

+ +
+
+ + diff --git a/tmp-browser-smoke/inline-flow/input-break.html b/tmp-browser-smoke/inline-flow/input-break.html new file mode 100644 index 000000000..7340b6d3c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/input-break.html @@ -0,0 +1,26 @@ + + + + + Inline Break Input + + +
+

+ Prefix text before the explicit break +
+ After break + + tail text after input +

+

Below paragraph stays below the break and inline input.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/next-eight.html b/tmp-browser-smoke/inline-flow/next-eight.html new file mode 100644 index 000000000..1b08c1109 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-eight.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Eight + +next seven + diff --git a/tmp-browser-smoke/inline-flow/next-five.html b/tmp-browser-smoke/inline-flow/next-five.html new file mode 100644 index 000000000..bdbbad7d2 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-five.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Five + +next five + diff --git a/tmp-browser-smoke/inline-flow/next-four.html b/tmp-browser-smoke/inline-flow/next-four.html new file mode 100644 index 000000000..98eba3135 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-four.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Four + +next four + diff --git a/tmp-browser-smoke/inline-flow/next-seven.html b/tmp-browser-smoke/inline-flow/next-seven.html new file mode 100644 index 000000000..66a79f3f0 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-seven.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Seven + +next seven + diff --git a/tmp-browser-smoke/inline-flow/next-six.html b/tmp-browser-smoke/inline-flow/next-six.html new file mode 100644 index 000000000..1f5569c6c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-six.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Six + +next six + diff --git a/tmp-browser-smoke/inline-flow/next-three.html b/tmp-browser-smoke/inline-flow/next-three.html new file mode 100644 index 000000000..7a92d92a6 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-three.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Three + +next three + diff --git a/tmp-browser-smoke/inline-flow/next-two.html b/tmp-browser-smoke/inline-flow/next-two.html new file mode 100644 index 000000000..23474f806 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next-two.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target Two + +next two + diff --git a/tmp-browser-smoke/inline-flow/next.html b/tmp-browser-smoke/inline-flow/next.html new file mode 100644 index 000000000..02b02bce9 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/next.html @@ -0,0 +1,8 @@ + + + + + Inline Flow Target + +next + diff --git a/tmp-browser-smoke/inline-flow/probe.ps1 b/tmp-browser-smoke/inline-flow/probe.ps1 new file mode 100644 index 000000000..8bd4fc1c7 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/probe.ps1 @@ -0,0 +1,115 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\inline-flow" +$port = 8138 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "inline-flow.png" +$browserOut = Join-Path $root "browser.stdout.txt" +$browserErr = Join-Path $root "browser.stderr.txt" +$serverOut = Join-Path $root "server.stdout.txt" +$serverErr = Join-Path $root "server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +$server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost probe server did not become ready" } +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--screenshot_png",$outPng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr +$pngReady = $false +for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $outPng) -and ((Get-Item $outPng).Length -gt 0)) { $pngReady = $true; break } +} +$analysis = $null +if ($pngReady) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + function New-Bounds { + [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + } + function Add-Pixel($o, $x, $y) { + if ($null -eq $o.min_x -or $x -lt $o.min_x) { $o.min_x = $x } + if ($null -eq $o.min_y -or $y -lt $o.min_y) { $o.min_y = $y } + if ($null -eq $o.max_x -or $x -gt $o.max_x) { $o.max_x = $x } + if ($null -eq $o.max_y -or $y -gt $o.max_y) { $o.max_y = $y } + $o.count++ + } + + $colors = [ordered]@{ + red = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + green = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + blue = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + } + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.R -ge 170 -and $c.G -le 90 -and $c.B -le 90) { + Add-Pixel $colors.red $x $y + } + if ($c.G -ge 120 -and $c.R -le 80 -and $c.B -le 110) { + Add-Pixel $colors.green $x $y + } + if ($c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120) { + Add-Pixel $colors.blue $x $y + } + } + } + + $chipColors = [ordered]@{ + red = $colors.red + green = $colors.green + blue = New-Bounds + } + if ($null -ne $colors.red.min_y) { + $bandMin = [Math]::Max(0, $colors.red.min_y - 8) + $bandMax = [Math]::Min($bmp.Height - 1, $colors.red.max_y + 8) + for ($y = $bandMin; $y -le $bandMax; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if ($c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120) { + Add-Pixel $chipColors.blue $x $y + } + } + } + } + + $analysis = [ordered]@{ + width = $bmp.Width + height = $bmp.Height + colors = $colors + chip_colors = $chipColors + same_line = if ($null -ne $chipColors.red.min_y -and $null -ne $chipColors.green.min_y -and $null -ne $chipColors.blue.min_y) { + ([math]::Abs($chipColors.red.min_y - $chipColors.green.min_y) -le 4) -and ([math]::Abs($chipColors.red.min_y - $chipColors.blue.min_y) -le 4) + } else { $false } + ordered_left_to_right = if ($null -ne $chipColors.red.max_x -and $null -ne $chipColors.green.min_x -and $null -ne $chipColors.green.max_x -and $null -ne $chipColors.blue.min_x) { + ($chipColors.red.max_x -lt $chipColors.green.min_x) -and ($chipColors.green.max_x -lt $chipColors.blue.min_x) + } else { $false } + } + } finally { + $bmp.Dispose() + } +} +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $outPng + screenshot_length = if (Test-Path $outPng) { (Get-Item $outPng).Length } else { 0 } + analysis = $analysis + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 7 diff --git a/tmp-browser-smoke/inline-flow/radio-button-link.html b/tmp-browser-smoke/inline-flow/radio-button-link.html new file mode 100644 index 000000000..88e24b796 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/radio-button-link.html @@ -0,0 +1,37 @@ + + + + + Mixed Inline Radio Button Link + + +
+

+ Lead text before the radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio, button, and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/radio-link.html b/tmp-browser-smoke/inline-flow/radio-link.html new file mode 100644 index 000000000..f1e0d0a1a --- /dev/null +++ b/tmp-browser-smoke/inline-flow/radio-link.html @@ -0,0 +1,28 @@ + + + + + Mixed Inline Radio Link + + +
+

+ Lead text before the radio + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/radio-pair-button-link.html b/tmp-browser-smoke/inline-flow/radio-pair-button-link.html new file mode 100644 index 000000000..6992b0990 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/radio-pair-button-link.html @@ -0,0 +1,46 @@ + + + + + Mixed Inline Radio Pair Button Link + + +
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before button + + bridge words before the link + + LINK + now + +

+

Below paragraph stays below the radio pair, button, and link flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/radio-pair-input-submit.html b/tmp-browser-smoke/inline-flow/radio-pair-input-submit.html new file mode 100644 index 000000000..a1b490b69 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/radio-pair-input-submit.html @@ -0,0 +1,51 @@ + + + + + Mixed Inline Radio Pair Input Submit + + +
+
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before the input + + bridge words before submit + +

+
+

Below paragraph stays below the radio pair, input, and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/radio-pair-submit.html b/tmp-browser-smoke/inline-flow/radio-pair-submit.html new file mode 100644 index 000000000..0cf9aa7aa --- /dev/null +++ b/tmp-browser-smoke/inline-flow/radio-pair-submit.html @@ -0,0 +1,42 @@ + + + + + Mixed Inline Radio Pair Submit + + +
+
+

+ Lead text before the first radio + + bridge words before the second radio + + bridge words before submit + +

+
+

Below paragraph stays below the radio pair and submit flow.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-input.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-input.html new file mode 100644 index 000000000..d8cd52d1d --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-input.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Input Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-pair.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-pair.html new file mode 100644 index 000000000..bd1ee3fc7 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-pair.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Pair Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-input.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-input.html new file mode 100644 index 000000000..15227253c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-input.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Input Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-input.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-input.html new file mode 100644 index 000000000..d6a1c8d00 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-input.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Input Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-eight-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-eight-link.html new file mode 100644 index 000000000..b02c936e7 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-eight-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Eight Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-five-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-five-link.html new file mode 100644 index 000000000..a92b3d205 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-five-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Five Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-four-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-four-link.html new file mode 100644 index 000000000..7bf875fba --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-four-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Four Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-link.html new file mode 100644 index 000000000..2c48b3eab --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-seven-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-seven-link.html new file mode 100644 index 000000000..89ae76a43 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-seven-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Seven Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-six-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-six-link.html new file mode 100644 index 000000000..7b5270892 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-six-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Six Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-three-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-three-link.html new file mode 100644 index 000000000..5c7fcf6b3 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-three-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Three Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-two-link.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-two-link.html new file mode 100644 index 000000000..567278a5c --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input-two-link.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Two Link Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input.html b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input.html new file mode 100644 index 000000000..2cc4c884a --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-checkbox-radio-pair-two-input.html @@ -0,0 +1,12 @@ + + + + + Inline Checkbox Radio Pair Two Input Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-radio-input.html b/tmp-browser-smoke/inline-flow/submitted-radio-input.html new file mode 100644 index 000000000..3b82a76b1 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-radio-input.html @@ -0,0 +1,12 @@ + + + + + Inline Radio Input Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted-radio-pair.html b/tmp-browser-smoke/inline-flow/submitted-radio-pair.html new file mode 100644 index 000000000..049585c41 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted-radio-pair.html @@ -0,0 +1,12 @@ + + + + + Inline Radio Pair Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/submitted.html b/tmp-browser-smoke/inline-flow/submitted.html new file mode 100644 index 000000000..e1f8b5d7f --- /dev/null +++ b/tmp-browser-smoke/inline-flow/submitted.html @@ -0,0 +1,12 @@ + + + + + Inline Break Submitted + + +
+

Submitted.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/wrapped-long.html b/tmp-browser-smoke/inline-flow/wrapped-long.html new file mode 100644 index 000000000..ba812e229 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/wrapped-long.html @@ -0,0 +1,22 @@ + + + + + Wrapped Long Inline Link + + +
+

+ Lead text before + + start + TOP + middle words that keep wrapping through + LOWER + final text after + +

+

Below paragraph stays below the long wrapped link.

+
+ + diff --git a/tmp-browser-smoke/inline-flow/wrapped.html b/tmp-browser-smoke/inline-flow/wrapped.html new file mode 100644 index 000000000..38eccc973 --- /dev/null +++ b/tmp-browser-smoke/inline-flow/wrapped.html @@ -0,0 +1,20 @@ + + + + + Wrapped Inline Flow + + +
+

+ Prefix text + RED + middle words that should wrap with + GREEN + and + go LINK now suffix text +

+

Below paragraph stays below wrapped inline content.

+
+ + diff --git a/tmp-browser-smoke/layout-smoke/LayoutProbeCommon.ps1 b/tmp-browser-smoke/layout-smoke/LayoutProbeCommon.ps1 new file mode 100644 index 000000000..86dcdfeab --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/LayoutProbeCommon.ps1 @@ -0,0 +1,182 @@ +$ErrorActionPreference = "Stop" + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | + Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return "" +} + +function Stop-VerifiedProcess($TargetPid) { + if (-not $TargetPid) { return } + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch "codex\\.js|@openai/codex") { + try { + Stop-Process -Id $TargetPid -Force -ErrorAction Stop + } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +function Wait-HttpReady($Url) { + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Reset-ProfileRoot($ProfileRoot) { + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null + @" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Wait-Screenshot($Path) { + $stableCount = 0 + $lastLength = -1 + $lastWriteTime = $null + for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (-not (Test-Path $Path)) { + continue + } + $item = Get-Item $Path + if ($item.Length -le 0) { + continue + } + if ($item.Length -eq $lastLength -and $lastWriteTime -ne $null -and $item.LastWriteTimeUtc -eq $lastWriteTime) { + $stableCount++ + if ($stableCount -ge 3) { return $true } + } else { + $stableCount = 0 + $lastLength = $item.Length + $lastWriteTime = $item.LastWriteTimeUtc + } + } + return $false +} + +function Read-Pixel($Bitmap, [int]$X, [int]$Y) { + $c = $Bitmap.GetPixel($X, $Y) + return [ordered]@{ + r = [int]$c.R + g = [int]$c.G + b = [int]$c.B + a = [int]$c.A + } +} + +function Test-ApproxColor($Pixel, [int]$R, [int]$G, [int]$B, [int]$Tolerance) { + return ([math]::Abs($Pixel.r - $R) -le $Tolerance) -and + ([math]::Abs($Pixel.g - $G) -le $Tolerance) -and + ([math]::Abs($Pixel.b - $B) -le $Tolerance) +} + +function Find-ColorBounds($Path, [scriptblock]$Predicate) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = 0; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + if ($maxX -lt 0 -or $maxY -lt 0) { + throw "target color not found in $Path" + } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } + finally { + $bmp.Dispose() + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +function Find-ColorBoundsRegion( + $Path, + [scriptblock]$Predicate, + [int]$MinX = 0, + [int]$MinY = 0, + [int]$MaxX = 2147483647, + [int]$MaxY = 2147483647 +) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minY = $bmp.Height + $maxX = -1 + $maxY = -1 + $scanMinX = [Math]::Max(0, $MinX) + $scanMinY = [Math]::Max(0, $MinY) + $scanMaxX = [Math]::Min($bmp.Width - 1, $MaxX) + $scanMaxY = [Math]::Min($bmp.Height - 1, $MaxY) + for ($y = $scanMinY; $y -le $scanMaxY; $y++) { + for ($x = $scanMinX; $x -le $scanMaxX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minY) { $minY = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + if ($maxX -lt 0 -or $maxY -lt 0) { + throw "target color not found in $Path" + } + return [ordered]@{ + left = $minX + top = $minY + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } + finally { + $bmp.Dispose() + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} diff --git a/tmp-browser-smoke/layout-smoke/absolute-position.html b/tmp-browser-smoke/layout-smoke/absolute-position.html new file mode 100644 index 000000000..fdbf2a2a4 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/absolute-position.html @@ -0,0 +1,46 @@ + + + + + Absolute Position Layout + + + +
LEFT
+
RIGHT
+
FLOW
+ + diff --git a/tmp-browser-smoke/layout-smoke/absolute-zindex.html b/tmp-browser-smoke/layout-smoke/absolute-zindex.html new file mode 100644 index 000000000..565d3c0d0 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/absolute-zindex.html @@ -0,0 +1,16 @@ + + + + + Absolute Z Index Layout + + +
+
Flow spacer
+
+ Low Overlay + High Overlay +
Body flow
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/background-image.html b/tmp-browser-smoke/layout-smoke/background-image.html new file mode 100644 index 000000000..ffcd1cb16 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/background-image.html @@ -0,0 +1,11 @@ + + + + + Background Image Layout - Lightpanda Browser + + +
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/background-position.html b/tmp-browser-smoke/layout-smoke/background-position.html new file mode 100644 index 000000000..b8695a20f --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/background-position.html @@ -0,0 +1,9 @@ + + +Background Position Layout - Lightpanda Browser + +
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/background-size.html b/tmp-browser-smoke/layout-smoke/background-size.html new file mode 100644 index 000000000..fd78e00aa --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/background-size.html @@ -0,0 +1,10 @@ + + +Background Size Layout - Lightpanda Browser + +
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/background-sprite.png b/tmp-browser-smoke/layout-smoke/background-sprite.png new file mode 100644 index 000000000..65e78462d Binary files /dev/null and b/tmp-browser-smoke/layout-smoke/background-sprite.png differ diff --git a/tmp-browser-smoke/layout-smoke/border-radius.html b/tmp-browser-smoke/layout-smoke/border-radius.html new file mode 100644 index 000000000..64909b15f --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/border-radius.html @@ -0,0 +1,11 @@ + + + + + Border Radius Layout - Lightpanda Browser + + +
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/box-shadow.html b/tmp-browser-smoke/layout-smoke/box-shadow.html new file mode 100644 index 000000000..f419b459d --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/box-shadow.html @@ -0,0 +1,28 @@ + + + + +Box Shadow - Lightpanda Browser + + + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-position-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-position-probe.ps1 new file mode 100644 index 000000000..f506a1a96 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-position-probe.ps1 @@ -0,0 +1,65 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8177 +$pageUrl = "http://127.0.0.1:$port/absolute-position.html" +$outPng = Join-Path $root "absolute-position.png" +$browserOut = Join-Path $root "absolute-position.browser.stdout.txt" +$browserErr = Join-Path $root "absolute-position.browser.stderr.txt" +$serverOut = Join-Path $root "absolute-position.server.stdout.txt" +$serverErr = Join-Path $root "absolute-position.server.stderr.txt" +$profileRoot = Join-Path $root "profile-absolute-position" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","960","--window_height","720","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "absolute position screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Find-ColorBounds $outPng { param($c) $c.R -ge 170 -and $c.B -ge 170 -and $c.G -le 120 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 170 -and $c.R -le 90 -and $c.B -le 90 } + $result = [ordered]@{ + red = $red + blue = $blue + green = $green + absolute_position_worked = ($red.left -le 40) -and + ($red.top -le 180) -and + ($blue.right -ge 820) -and + ($blue.top -le 180) -and + ($green.top -ge 240) + } + if (-not $result.absolute_position_worked) { + throw "absolute position probe did not observe docked corner boxes and later flow box" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-zindex-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-zindex-probe.ps1 new file mode 100644 index 000000000..52ca73bcf --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-absolute-zindex-probe.ps1 @@ -0,0 +1,85 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +$tabCommon = Join-Path $repo "tmp-browser-smoke\tabs\TabProbeCommon.ps1" +. $common +. $tabCommon + +$port = 8182 +$pageUrl = "http://127.0.0.1:$port/absolute-zindex.html" +$outPng = Join-Path $root "absolute-zindex.png" +$browserOut = Join-Path $root "absolute-zindex.browser.stdout.txt" +$browserErr = Join-Path $root "absolute-zindex.browser.stderr.txt" +$serverOut = Join-Path $root "absolute-zindex.server.stdout.txt" +$serverErr = Join-Path $root "absolute-zindex.server.stderr.txt" +$profileRoot = Join-Path $root "profile-absolute-zindex" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","520","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "browser window handle was not ready" } + Show-SmokeWindow $hwnd + $initialTitle = Wait-TabTitle $browser.Id "Absolute Z Index Layout" 40 + if (-not $initialTitle) { throw "initial title did not stabilize" } + if (-not (Wait-Screenshot $outPng)) { throw "absolute z-index screenshot did not become ready" } + + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 130 } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + + $anchoredTop = ($red.top -lt 190) -and ($blue.top -lt 190) + $flowBelow = ($green.top -ge ($red.bottom + 40)) + + $clickX = [int][Math]::Floor(($red.left + $red.right) / 2) + $clickY = [int][Math]::Floor(($red.top + $red.bottom) / 2) + $clickPoint = Invoke-SmokeClientClick $hwnd $clickX $clickY + $afterTitle = Wait-TabTitle $browser.Id "Z Index High Target" 40 + if (-not $afterTitle) { throw "overlap click did not navigate to high target" } + + $result = [ordered]@{ + title_before = $initialTitle + title_after = $afterTitle + blue = $blue + red = $red + green = $green + click_point = $clickPoint + anchored_top = $anchoredTop + flow_below = $flowBelow + overlap_click_worked = [bool]$afterTitle + } + $result.absolute_zindex_worked = $result.anchored_top -and $result.flow_below -and $result.overlap_click_worked + if (-not $result.absolute_zindex_worked) { + throw "absolute z-index probe did not observe correct positioning and overlap behavior" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-background-image-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-background-image-probe.ps1 new file mode 100644 index 000000000..1680ff44e --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-background-image-probe.ps1 @@ -0,0 +1,108 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ + left = $minFoundX + top = $minFoundY + right = $maxFoundX + bottom = $maxFoundY + width = $maxFoundX - $minFoundX + 1 + height = $maxFoundY - $minFoundY + 1 + } + } + finally { + $bmp.Dispose() + } +} + +$port = 8186 +$pageUrl = "http://127.0.0.1:$port/background-image.html" +$outPng = Join-Path $root "background-image.png" +$browserOut = Join-Path $root "background-image.browser.stdout.txt" +$browserErr = Join-Path $root "background-image.browser.stderr.txt" +$serverOut = Join-Path $root "background-image.server.stdout.txt" +$serverErr = Join-Path $root "background-image.server.stderr.txt" +$profileRoot = Join-Path $root "profile-background-image" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","640","--window_height","320","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "background image screenshot did not become ready" } + + $blue = Find-ColorBoundsInRegion $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 140 } 120 120 520 200 + $red = Find-ColorBoundsInRegion $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } 120 210 520 310 + + $blueRepeated = $blue.width -ge 220 + $redSingle = $red.width -ge 34 -and $red.width -le 50 + $stacked = $red.top -gt $blue.bottom + $aligned = [Math]::Abs($blue.left - $red.left) -le 2 + + $result = [ordered]@{ + blue = $blue + red = $red + blue_repeated = $blueRepeated + red_single = $redSingle + stacked = $stacked + aligned = $aligned + } + $result.background_image_worked = $result.blue_repeated -and $result.red_single -and $result.stacked -and $result.aligned + if (-not $result.background_image_worked) { + throw "background image probe did not observe repeat-x and no-repeat sprite painting" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-background-position-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-background-position-probe.ps1 new file mode 100644 index 000000000..c06f94edf --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-background-position-probe.ps1 @@ -0,0 +1,84 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ left=$minFoundX; top=$minFoundY; right=$maxFoundX; bottom=$maxFoundY; width=$maxFoundX-$minFoundX+1; height=$maxFoundY-$minFoundY+1 } + } + finally { $bmp.Dispose() } +} + +$port = 8189 +$pageUrl = "http://127.0.0.1:$port/background-position.html" +$outPng = Join-Path $root "background-position.png" +$browserOut = Join-Path $root "background-position.browser.stdout.txt" +$browserErr = Join-Path $root "background-position.browser.stderr.txt" +$serverOut = Join-Path $root "background-position.server.stdout.txt" +$serverErr = Join-Path $root "background-position.server.stderr.txt" +$profileRoot = Join-Path $root "profile-background-position" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","640","--window_height","700","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "background position screenshot did not become ready" } + $predicate = { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $percent = Find-ColorBoundsInRegion $outPng $predicate 160 120 360 280 + $center = Find-ColorBoundsInRegion $outPng $predicate 220 280 420 460 + $end = Find-ColorBoundsInRegion $outPng $predicate 320 460 520 660 + $result = [ordered]@{ + percent = $percent + center = $center + end = $end + widths_ok = ($percent.width -ge 40 -and $percent.width -le 42 -and $center.width -ge 40 -and $center.width -le 42 -and $end.width -ge 40 -and $end.width -le 42) + heights_ok = ($percent.height -ge 80 -and $percent.height -le 82 -and $center.height -ge 80 -and $center.height -le 82 -and $end.height -ge 80 -and $end.height -le 82) + x_order_ok = ($percent.left + 30 -le $center.left) -and ($center.left + 50 -le $end.left) + y_order_ok = ($center.top -ge $percent.top + 120) -and ($end.top -ge $center.top + 120) + } + $result.background_position_worked = $result.widths_ok -and $result.heights_ok -and $result.x_order_ok -and $result.y_order_ok + if (-not $result.background_position_worked) { throw "background position probe did not observe percent, center, and end alignment differences" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-background-size-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-background-size-probe.ps1 new file mode 100644 index 000000000..bfb353dfe --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-background-size-probe.ps1 @@ -0,0 +1,86 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ left=$minFoundX; top=$minFoundY; right=$maxFoundX; bottom=$maxFoundY; width=$maxFoundX-$minFoundX+1; height=$maxFoundY-$minFoundY+1 } + } + finally { $bmp.Dispose() } +} + +$port = 8191 +$pageUrl = "http://127.0.0.1:$port/background-size.html" +$outPng = Join-Path $root "background-size.png" +$browserOut = Join-Path $root "background-size.browser.stdout.txt" +$browserErr = Join-Path $root "background-size.browser.stderr.txt" +$serverOut = Join-Path $root "background-size.server.stdout.txt" +$serverErr = Join-Path $root "background-size.server.stderr.txt" +$profileRoot = Join-Path $root "profile-background-size" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","640","--window_height","860","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "background size screenshot did not become ready" } + $predicate = { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $contain = Find-ColorBoundsInRegion $outPng $predicate 220 120 420 280 + $cover = Find-ColorBoundsInRegion $outPng $predicate 160 280 500 460 + $explicit = Find-ColorBoundsInRegion $outPng $predicate 220 450 420 620 + $percent = Find-ColorBoundsInRegion $outPng $predicate 180 620 460 820 + $result = [ordered]@{ + contain = $contain + cover = $cover + explicit = $explicit + percent = $percent + contain_ok = ($contain.width -ge 59 -and $contain.width -le 61 -and $contain.height -ge 119 -and $contain.height -le 121) + cover_ok = ($cover.width -ge 236 -and $cover.width -le 240 -and $cover.height -ge 119 -and $cover.height -le 121) + explicit_ok = ($explicit.width -ge 79 -and $explicit.width -le 81 -and $explicit.height -ge 119 -and $explicit.height -le 121) + percent_ok = ($percent.width -ge 176 -and $percent.width -le 179 -and $percent.height -ge 119 -and $percent.height -le 121) + } + $result.background_size_worked = $result.contain_ok -and $result.cover_ok -and $result.explicit_ok -and $result.percent_ok + if (-not $result.background_size_worked) { throw "background size probe did not observe contain, cover, explicit, and percent sizing differences" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-border-radius-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-border-radius-probe.ps1 new file mode 100644 index 000000000..22c1175a0 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-border-radius-probe.ps1 @@ -0,0 +1,81 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8187 +$pageUrl = "http://127.0.0.1:$port/border-radius.html" +$outPng = Join-Path $root "border-radius.png" +$browserOut = Join-Path $root "border-radius.browser.stdout.txt" +$browserErr = Join-Path $root "border-radius.browser.stderr.txt" +$serverOut = Join-Path $root "border-radius.server.stdout.txt" +$serverErr = Join-Path $root "border-radius.server.stderr.txt" +$profileRoot = Join-Path $root "profile-border-radius" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","640","--window_height","340","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "border radius screenshot did not become ready" } + + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -ge 80 -and $c.G -le 140 } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $blueCorner = Read-Pixel $bmp ($blue.left + 1) ($blue.top + 1) + $blueInner = Read-Pixel $bmp ($blue.left + 16) ($blue.top + 16) + $redCorner = Read-Pixel $bmp ($red.left + 1) ($red.top + 1) + $blueCornerCleared = (Test-ApproxColor $blueCorner 255 255 255 20) + $blueInnerFilled = (Test-ApproxColor $blueInner 61 115 230 40) + $redCornerFilled = (Test-ApproxColor $redCorner 216 59 59 40) + + $result = [ordered]@{ + blue = $blue + red = $red + blue_corner = $blueCorner + blue_inner = $blueInner + red_corner = $redCorner + blue_corner_cleared = $blueCornerCleared + blue_inner_filled = $blueInnerFilled + red_corner_filled = $redCornerFilled + } + $result.border_radius_worked = $result.blue_corner_cleared -and $result.blue_inner_filled -and $result.red_corner_filled + if (-not $result.border_radius_worked) { + throw "border radius probe did not observe rounded-vs-square corner behavior" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-box-shadow-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-box-shadow-probe.ps1 new file mode 100644 index 000000000..da831da74 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-box-shadow-probe.ps1 @@ -0,0 +1,71 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8231 +$pageUrl = "http://127.0.0.1:$port/box-shadow.html" +$outPng = Join-Path $root "box-shadow.png" +$browserOut = Join-Path $root "box-shadow.browser.stdout.txt" +$browserErr = Join-Path $root "box-shadow.browser.stderr.txt" +$serverOut = Join-Path $root "box-shadow.server.stdout.txt" +$serverErr = Join-Path $root "box-shadow.server.stderr.txt" +$profileRoot = Join-Path $root "profile-box-shadow" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "box shadow smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","320","--window_height","220","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "box shadow screenshot did not become ready" } + + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $shadowPixel = Read-Pixel $bmp 200 170 + $shadowEdgePixel = Read-Pixel $bmp 210 180 + + $result = [ordered]@{ + shadow_pixel = $shadowPixel + shadow_edge_pixel = $shadowEdgePixel + shadow_pixel_dark = ($shadowPixel.r -lt 240 -and $shadowPixel.g -lt 240 -and $shadowPixel.b -lt 240) + shadow_edge_dark = ($shadowEdgePixel.r -lt 240 -and $shadowEdgePixel.g -lt 240 -and $shadowEdgePixel.b -lt 240) + shadow_visible = ($shadowPixel.r -lt 240 -and $shadowEdgePixel.r -lt 240) + } + $result.box_shadow_worked = $result.shadow_pixel_dark -and $result.shadow_visible + if (-not $result.box_shadow_worked) { + throw "box shadow probe did not observe the expected offset shadow" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-fixed-position-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-fixed-position-probe.ps1 new file mode 100644 index 000000000..238d0731e --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-fixed-position-probe.ps1 @@ -0,0 +1,65 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8182 +$pageUrl = "http://127.0.0.1:$port/fixed-position.html" +$outPng = Join-Path $root "fixed-position.png" +$browserOut = Join-Path $root "fixed-position.browser.stdout.txt" +$browserErr = Join-Path $root "fixed-position.browser.stderr.txt" +$serverOut = Join-Path $root "fixed-position.server.stdout.txt" +$serverErr = Join-Path $root "fixed-position.server.stderr.txt" +$profileRoot = Join-Path $root "profile-fixed-position" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","960","--window_height","720","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "fixed position screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + $result = [ordered]@{ + red = $red + blue = $blue + green = $green + fixed_position_worked = ($red.left -le 50) -and + ($red.top -le 180) -and + ($blue.right -ge 840) -and + ($blue.top -le 180) -and + ($green.top -ge 280) + } + if (-not $result.fixed_position_worked) { + throw "fixed position probe did not observe viewport-anchored fixed controls and later flow content" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-content-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-content-probe.ps1 new file mode 100644 index 000000000..1f26bf5ef --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-content-probe.ps1 @@ -0,0 +1,53 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8203 +$pageUrl = "http://127.0.0.1:$port/flex-align-content.html" +$outPng = Join-Path $root "flex-align-content.png" +$browserOut = Join-Path $root "flex-align-content.browser.stdout.txt" +$browserErr = Join-Path $root "flex-align-content.browser.stderr.txt" +$serverOut = Join-Path $root "flex-align-content.server.stdout.txt" +$serverErr = Join-Path $root "flex-align-content.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-align-content" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "flex align-content smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","480","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex align-content screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + $result = [ordered]@{ + red = $red + blue = $blue + green = $green + same_row = ([math]::Abs($red.top - $blue.top) -le 4) + wrapped = ($green.top -ge ($red.bottom + 80)) + centered = ($green.left -ge 110) -and ($green.left -le 180) + } + $result.flex_align_content_worked = $result.same_row -and $result.wrapped -and $result.centered + if (-not $result.flex_align_content_worked) { throw "flex align-content probe did not observe wrapped spacing" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-self-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-self-probe.ps1 new file mode 100644 index 000000000..12ef9dcc7 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-align-self-probe.ps1 @@ -0,0 +1,101 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +$tabCommon = Join-Path $repo "tmp-browser-smoke\tabs\TabProbeCommon.ps1" +. $common +. $tabCommon + +function Find-ColorBoundsBelowY($Path, [int]$MinY, [scriptblock]$Predicate) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minYFound = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = $MinY; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minYFound) { $minYFound = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + if ($maxX -lt 0 -or $maxY -lt 0) { + throw "target color not found in $Path" + } + return [ordered]@{ + left = $minX + top = $minYFound + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } + finally { + $bmp.Dispose() + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +$port = 8204 +$pageUrl = "http://127.0.0.1:$port/flex-align-self.html" +$outPng = Join-Path $root "flex-align-self.png" +$browserOut = Join-Path $root "flex-align-self.browser.stdout.txt" +$browserErr = Join-Path $root "flex-align-self.browser.stderr.txt" +$serverOut = Join-Path $root "flex-align-self.server.stdout.txt" +$serverErr = Join-Path $root "flex-align-self.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-align-self" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "flex align-self smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","540","--window_height","300","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex align-self screenshot did not become ready" } + $title = Wait-TabTitle $browser.Id "Flex Align Self" 20 + if (-not $title) { throw "flex align-self title did not stabilize" } + if ($title -notmatch 'Flex Align Self\s+(\d+)\s+(\d+)\s+(\d+)') { throw "flex align-self title did not include measured tops" } + $redTop = [int]$Matches[1] + $grayTop = [int]$Matches[2] + $blueTop = [int]$Matches[3] + $result = [ordered]@{ + title = $title + red_top = $redTop + gray_top = $grayTop + blue_top = $blueTop + aligned = ($redTop -lt $grayTop) -and ($grayTop -lt $blueTop) + gray_centered = ($grayTop -gt ($redTop + 6)) -and ($blueTop -gt ($grayTop + 6)) + } + $result.flex_align_self_worked = $result.aligned -and $result.gray_centered + if (-not $result.flex_align_self_worked) { throw "flex align-self probe did not observe per-item vertical alignment" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-center-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-center-probe.ps1 new file mode 100644 index 000000000..4aff0fad9 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-center-probe.ps1 @@ -0,0 +1,70 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8177 +$pageUrl = "http://127.0.0.1:$port/flex-center.html" +$outPng = Join-Path $root "flex-center.png" +$browserOut = Join-Path $root "flex-center.browser.stdout.txt" +$browserErr = Join-Path $root "flex-center.browser.stderr.txt" +$serverOut = Join-Path $root "flex-center.server.stdout.txt" +$serverErr = Join-Path $root "flex-center.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-center" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","960","--window_height","720","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex center screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $redCenterX = ($red.left + $red.right) / 2.0 + $blueCenterX = ($blue.left + $blue.right) / 2.0 + $redCenterY = ($red.top + $red.bottom) / 2.0 + $blueCenterY = ($blue.top + $blue.bottom) / 2.0 + $result = [ordered]@{ + red = $red + blue = $blue + red_center_x = $redCenterX + blue_center_x = $blueCenterX + red_center_y = $redCenterY + blue_center_y = $blueCenterY + flex_center_worked = ([math]::Abs($redCenterX - 480) -le 80) -and + ([math]::Abs($blueCenterX - 480) -le 80) -and + ($redCenterY -gt 120) -and + ($blueCenterY -gt ($redCenterY + 40)) + } + if (-not $result.flex_center_worked) { + throw "flex center probe did not observe centered hero blocks" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-grow-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-grow-probe.ps1 new file mode 100644 index 000000000..3032a4dd2 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-grow-probe.ps1 @@ -0,0 +1,74 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/flex-grow.html" +$outPng = Join-Path $root "flex-grow.png" +$browserOut = Join-Path $root "flex-grow.browser.stdout.txt" +$browserErr = Join-Path $root "flex-grow.browser.stderr.txt" +$serverOut = Join-Path $root "flex-grow.server.stdout.txt" +$serverErr = Join-Path $root "flex-grow.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-grow" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","760","--window_height","360","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex grow screenshot did not become ready" } + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $red = Read-Pixel $bmp 100 170 + $grayMid = Read-Pixel $bmp 330 176 + $grayFar = Read-Pixel $bmp 430 176 + $blue = Read-Pixel $bmp 555 170 + + $result = [ordered]@{ + red = $red + gray_mid = $grayMid + gray_far = $grayFar + blue = $blue + red_visible = Test-ApproxColor $red 220 50 50 25 + gray_expanded = (Test-ApproxColor $grayMid 160 160 160 25) -and (Test-ApproxColor $grayFar 160 160 160 25) + blue_visible = Test-ApproxColor $blue 40 110 220 30 + } + $result.flex_grow_worked = $result.red_visible -and $result.gray_expanded -and $result.blue_visible + if (-not $result.flex_grow_worked) { + throw "flex grow probe did not observe an expanded middle item with docked siblings" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + $bmp.Dispose() + } + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-order-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-order-probe.ps1 new file mode 100644 index 000000000..9436b92aa --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-order-probe.ps1 @@ -0,0 +1,65 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +$tabCommon = Join-Path $repo "tmp-browser-smoke\tabs\TabProbeCommon.ps1" +. $common +. $tabCommon + +$port = 8201 +$pageUrl = "http://127.0.0.1:$port/flex-order.html" +$outPng = Join-Path $root "flex-order.png" +$browserOut = Join-Path $root "flex-order.browser.stdout.txt" +$browserErr = Join-Path $root "flex-order.browser.stderr.txt" +$serverOut = Join-Path $root "flex-order.server.stdout.txt" +$serverErr = Join-Path $root "flex-order.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-order" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "flex order smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","480","--window_height","240","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "browser window handle was not ready" } + Show-SmokeWindow $hwnd + if (-not (Wait-TabTitle $browser.Id "Flex Order - Lightpanda Browser" 20)) { throw "flex order initial title did not stabilize" } + if (-not (Wait-Screenshot $outPng)) { throw "flex order screenshot did not become ready" } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $clickX = [int][Math]::Floor(($blue.left + $blue.right) / 2) + $clickY = [int][Math]::Floor(($blue.top + $blue.bottom) / 2) + Invoke-SmokeClientClick $hwnd $clickX $clickY | Out-Null + $afterTitle = Wait-TabTitle $browser.Id "Flex Order Blue Target - Lightpanda Browser" 30 + if (-not $afterTitle) { throw "flex order click did not navigate to blue target" } + $result = [ordered]@{ + title_before = "Flex Order - Lightpanda Browser" + title_after = $afterTitle + blue = $blue + green = $green + red = $red + blue_first = ($blue.left -lt $green.left) -and ($green.left -lt $red.left) + click_point = [ordered]@{ x = $clickX; y = $clickY } + order_worked = [bool]$afterTitle -and ($blue.left -lt $green.left) -and ($green.left -lt $red.left) + } + if (-not $result.order_worked) { throw "flex order probe did not observe ordered chips and click navigation" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-overflow-hidden-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-overflow-hidden-probe.ps1 new file mode 100644 index 000000000..334e74bdd --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-overflow-hidden-probe.ps1 @@ -0,0 +1,51 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8193 +$pageUrl = "http://127.0.0.1:$port/flex-overflow-hidden.html" +$outPng = Join-Path $root "flex-overflow-hidden.png" +$browserOut = Join-Path $root "flex-overflow-hidden.browser.stdout.txt" +$browserErr = Join-Path $root "flex-overflow-hidden.browser.stderr.txt" +$serverOut = Join-Path $root "flex-overflow-hidden.server.stdout.txt" +$serverErr = Join-Path $root "flex-overflow-hidden.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-overflow-hidden" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "flex overflow hidden smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","460","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex overflow hidden screenshot did not become ready" } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 130 -and $c.R -le 90 -and $c.B -le 110 } + $result = [ordered]@{ + blue = $blue + green = $green + blue_size_ok = ($blue.width -ge 140 -and $blue.width -le 142 -and $blue.height -ge 80 -and $blue.height -le 82) + green_size_ok = ($green.width -ge 120 -and $green.width -le 122 -and $green.height -ge 24 -and $green.height -le 26) + flow_ok = ($green.top -ge $blue.bottom + 14) + } + $result.flex_overflow_hidden_worked = $result.blue_size_ok -and $result.green_size_ok -and $result.flow_ok + if (-not $result.flex_overflow_hidden_worked) { throw "flex overflow hidden probe did not observe clipped flex descendants and preserved later flow" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-row-wrap-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-row-wrap-probe.ps1 new file mode 100644 index 000000000..6fd7ce355 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-row-wrap-probe.ps1 @@ -0,0 +1,64 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8179 +$pageUrl = "http://127.0.0.1:$port/flex-row-wrap.html" +$outPng = Join-Path $root "flex-row-wrap.png" +$browserOut = Join-Path $root "flex-row-wrap.browser.stdout.txt" +$browserErr = Join-Path $root "flex-row-wrap.browser.stderr.txt" +$serverOut = Join-Path $root "flex-row-wrap.server.stdout.txt" +$serverErr = Join-Path $root "flex-row-wrap.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-row-wrap" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex row wrap screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + $result = [ordered]@{ + red = $red + blue = $blue + green = $green + same_row = ([math]::Abs($red.top - $blue.top) -le 6) + wrapped = ($green.top -ge ($red.bottom + 8)) + centered = ($green.left -ge 120) -and ($green.left -le 220) + } + $result.flex_row_wrap_worked = $result.same_row -and $result.wrapped -and $result.centered + if (-not $result.flex_row_wrap_worked) { + throw "flex row wrap probe did not observe centered wrapped chips" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-flex-shrink-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-shrink-probe.ps1 new file mode 100644 index 000000000..aa2a5c7d7 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-flex-shrink-probe.ps1 @@ -0,0 +1,100 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +$tabCommon = Join-Path $repo "tmp-browser-smoke\tabs\TabProbeCommon.ps1" +. $common +. $tabCommon + +function Find-ColorBoundsBelowY($Path, [int]$MinY, [scriptblock]$Predicate) { + Add-Type -AssemblyName System.Drawing + for ($attempt = 0; $attempt -lt 20; $attempt++) { + try { + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minX = $bmp.Width + $minYFound = $bmp.Height + $maxX = -1 + $maxY = -1 + for ($y = $MinY; $y -lt $bmp.Height; $y++) { + for ($x = 0; $x -lt $bmp.Width; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minX) { $minX = $x } + if ($y -lt $minYFound) { $minYFound = $y } + if ($x -gt $maxX) { $maxX = $x } + if ($y -gt $maxY) { $maxY = $y } + } + } + } + if ($maxX -lt 0 -or $maxY -lt 0) { + throw "target color not found in $Path" + } + return [ordered]@{ + left = $minX + top = $minYFound + right = $maxX + bottom = $maxY + width = $maxX - $minX + 1 + height = $maxY - $minY + 1 + } + } + finally { + $bmp.Dispose() + } + } catch { + if ($attempt -eq 19) { throw } + Start-Sleep -Milliseconds 200 + } + } +} + +$port = 8202 +$pageUrl = "http://127.0.0.1:$port/flex-shrink.html" +$outPng = Join-Path $root "flex-shrink.png" +$browserOut = Join-Path $root "flex-shrink.browser.stdout.txt" +$browserErr = Join-Path $root "flex-shrink.browser.stderr.txt" +$serverOut = Join-Path $root "flex-shrink.server.stdout.txt" +$serverErr = Join-Path $root "flex-shrink.server.stderr.txt" +$profileRoot = Join-Path $root "profile-flex-shrink" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "flex shrink smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","480","--window_height","240","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "flex shrink screenshot did not become ready" } + $red = Find-ColorBoundsBelowY $outPng 120 { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + $gray = Find-ColorBoundsBelowY $outPng 120 { param($c) $c.R -ge 130 -and $c.R -le 190 -and $c.G -ge 130 -and $c.G -le 190 -and $c.B -ge 130 -and $c.B -le 190 } + $blue = Find-ColorBoundsBelowY $outPng 120 { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + $result = [ordered]@{ + red = $red + gray = $gray + blue = $blue + red_width = $red.width + gray_width = $gray.width + blue_width = $blue.width + red_visible = ($red.width -eq 120) + shrink_visible = ($gray.width -eq 90) -and ($blue.width -eq 90) + } + $result.flex_shrink_worked = $result.red_visible -and $result.shrink_visible + if (-not $result.flex_shrink_worked) { throw "flex shrink probe did not observe the expected width reduction" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-float-dock-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-float-dock-probe.ps1 new file mode 100644 index 000000000..729118503 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-float-dock-probe.ps1 @@ -0,0 +1,66 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/float-dock.html" +$outPng = Join-Path $root "float-dock.png" +$browserOut = Join-Path $root "float-dock.browser.stdout.txt" +$browserErr = Join-Path $root "float-dock.browser.stderr.txt" +$serverOut = Join-Path $root "float-dock.server.stdout.txt" +$serverErr = Join-Path $root "float-dock.server.stderr.txt" +$profileRoot = Join-Path $root "profile-float-dock" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","760","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "float dock screenshot did not become ready" } + + $redBounds = Find-ColorBounds $outPng { param($c) ($c.R -ge 190 -and $c.R -le 235) -and ($c.G -ge 50 -and $c.G -le 110) -and ($c.B -ge 50 -and $c.B -le 110) } + $blueBounds = Find-ColorBounds $outPng { param($c) ($c.R -ge 30 -and $c.R -le 80) -and ($c.G -ge 90 -and $c.G -le 140) -and ($c.B -ge 170 -and $c.B -le 230) } + $greenBounds = Find-ColorBounds $outPng { param($c) ($c.R -ge 70 -and $c.R -le 120) -and ($c.G -ge 140 -and $c.G -le 190) -and ($c.B -ge 70 -and $c.B -le 120) } + + $result = [ordered]@{ + red = $redBounds + blue = $blueBounds + green = $greenBounds + left_docked = $redBounds.left -lt 100 + right_docked = $blueBounds.right -gt 690 + body_below = ($greenBounds.top -ge $redBounds.bottom) -and ($greenBounds.top -ge $blueBounds.bottom) + } + $result.float_dock_worked = $result.left_docked -and $result.right_docked -and $result.body_below + if (-not $result.float_dock_worked) { + throw "float dock probe did not observe left/right floated chips with body flow below them" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-intrinsic-image-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-intrinsic-image-probe.ps1 new file mode 100644 index 000000000..24801fc36 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-intrinsic-image-probe.ps1 @@ -0,0 +1,83 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ left=$minFoundX; top=$minFoundY; right=$maxFoundX; bottom=$maxFoundY; width=$maxFoundX-$minFoundX+1; height=$maxFoundY-$minFoundY+1 } + } + finally { $bmp.Dispose() } +} + +$port = 8188 +$pageUrl = "http://127.0.0.1:$port/intrinsic-image.html" +$outPng = Join-Path $root "intrinsic-image.png" +$browserOut = Join-Path $root "intrinsic-image.browser.stdout.txt" +$browserErr = Join-Path $root "intrinsic-image.browser.stderr.txt" +$serverOut = Join-Path $root "intrinsic-image.server.stdout.txt" +$serverErr = Join-Path $root "intrinsic-image.server.stderr.txt" +$profileRoot = Join-Path $root "profile-intrinsic-image" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "intrinsic image screenshot did not become ready" } + $predicate = { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $natural = Find-ColorBoundsInRegion $outPng $predicate 40 120 140 240 + $widthOnly = Find-ColorBoundsInRegion $outPng $predicate 40 220 120 300 + $heightOnly = Find-ColorBoundsInRegion $outPng $predicate 40 290 120 360 + $result = [ordered]@{ + natural = $natural + width_only = $widthOnly + height_only = $heightOnly + natural_size_ok = ($natural.width -ge 40 -and $natural.width -le 42 -and $natural.height -ge 80 -and $natural.height -le 82) + width_only_size_ok = ($widthOnly.width -ge 20 -and $widthOnly.width -le 22 -and $widthOnly.height -ge 40 -and $widthOnly.height -le 42) + height_only_size_ok = ($heightOnly.width -ge 20 -and $heightOnly.width -le 22 -and $heightOnly.height -ge 40 -and $heightOnly.height -le 42) + } + $result.intrinsic_image_worked = $result.natural_size_ok -and $result.width_only_size_ok -and $result.height_only_size_ok + if (-not $result.intrinsic_image_worked) { throw "intrinsic image probe did not observe natural and aspect-ratio-scaled image boxes" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-legacy-table-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-legacy-table-probe.ps1 new file mode 100644 index 000000000..d9543b285 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-legacy-table-probe.ps1 @@ -0,0 +1,104 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ + left = $minFoundX + top = $minFoundY + right = $maxFoundX + bottom = $maxFoundY + width = $maxFoundX - $minFoundX + 1 + height = $maxFoundY - $minFoundY + 1 + } + } + finally { + $bmp.Dispose() + } +} + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/legacy-table.html" +$outPng = Join-Path $root "legacy-table.png" +$browserOut = Join-Path $root "legacy-table.browser.stdout.txt" +$browserErr = Join-Path $root "legacy-table.browser.stderr.txt" +$serverOut = Join-Path $root "legacy-table.server.stdout.txt" +$serverErr = Join-Path $root "legacy-table.server.stderr.txt" +$profileRoot = Join-Path $root "profile-legacy-table" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","960","--window_height","540","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "legacy table screenshot did not become ready" } + + $logoBounds = Find-ColorBoundsInRegion $outPng { param($c) ($c.R -ge 60 -and $c.R -le 110) -and ($c.G -ge 120 -and $c.G -le 160) -and ($c.B -ge 220 -and $c.B -le 255) } 220 80 760 240 + $shellBounds = Find-ColorBoundsInRegion $outPng { param($c) ($c.R -ge 195 -and $c.R -le 215) -and ($c.G -ge 195 -and $c.G -le 215) -and ($c.B -ge 195 -and $c.B -le 215) } 180 220 780 340 + $sideBounds = Find-ColorBoundsInRegion $outPng { param($c) ($c.R -ge 20 -and $c.R -le 60) -and ($c.G -ge 140 -and $c.G -le 180) -and ($c.B -ge 70 -and $c.B -le 120) } 650 220 900 340 + + $result = [ordered]@{ + logo = $logoBounds + shell = $shellBounds + side = $sideBounds + logo_centered = ($logoBounds.left -ge 320) -and ($logoBounds.left -le 380) + shell_centered = ($shellBounds.left -ge 240) -and ($shellBounds.left -le 300) -and ($shellBounds.width -ge 430) + side_right = $sideBounds.left -ge ($shellBounds.right - 4) + } + $result.legacy_table_worked = $result.logo_centered -and $result.shell_centered -and $result.side_right + if (-not $result.legacy_table_worked) { + throw "legacy table probe did not observe centered table layout with a right-side cell" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-min-max-height-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-min-max-height-probe.ps1 new file mode 100644 index 000000000..1f1f376e5 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-min-max-height-probe.ps1 @@ -0,0 +1,53 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8194 +$pageUrl = "http://127.0.0.1:$port/min-max-height.html" +$outPng = Join-Path $root "min-max-height.png" +$browserOut = Join-Path $root "min-max-height.browser.stdout.txt" +$browserErr = Join-Path $root "min-max-height.browser.stderr.txt" +$serverOut = Join-Path $root "min-max-height.server.stdout.txt" +$serverErr = Join-Path $root "min-max-height.server.stderr.txt" +$profileRoot = Join-Path $root "profile-min-max-height" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "min max height smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","460","--window_height","560","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "min max height screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 130 -and $c.R -le 90 -and $c.B -le 110 } + $result = [ordered]@{ + red = $red + blue = $blue + green = $green + min_height_ok = ($red.width -ge 120 -and $red.width -le 122 -and $red.height -ge 90 -and $red.height -le 92) + max_height_ok = ($blue.width -ge 120 -and $blue.width -le 122 -and $blue.height -ge 80 -and $blue.height -le 82) + flow_ok = ($green.top -ge $blue.bottom + 14) + } + $result.min_max_height_worked = $result.min_height_ok -and $result.max_height_ok -and $result.flow_ok + if (-not $result.min_max_height_worked) { throw "min max height probe did not observe generic block min-height and max-height behavior" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-link-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-link-probe.ps1 new file mode 100644 index 000000000..9ce6087b1 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-link-probe.ps1 @@ -0,0 +1,115 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$port = 8195 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$outPng = Join-Path $root "overflow-hidden-link.png" +$browserOut = Join-Path $root "overflow-hidden-link.browser.stdout.txt" +$browserErr = Join-Path $root "overflow-hidden-link.browser.stderr.txt" +$serverOut = Join-Path $root "overflow-hidden-link.server.stdout.txt" +$serverErr = Join-Path $root "overflow-hidden-link.server.stderr.txt" +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\LayoutProbeCommon.ps1" +. "$PSScriptRoot\..\common\Win32Input.ps1" + +$profileRoot = Join-Path $root "profile-overflow-hidden-link" +Reset-ProfileRoot $profileRoot + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$titleBefore = $null +$titleAfterFooter = $null +$titleAfterBlue = $null +$footerBlocked = $false +$visibleNavigated = $false +$failure = $null +$blue = $null +$red = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList (Join-Path $root "layout_server.py"),$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + $pageUrl = "http://127.0.0.1:$port/overflow-hidden-link.html" + if (-not (Wait-HttpReady $pageUrl)) { throw "overflow hidden link smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + if (-not (Wait-Screenshot $outPng)) { throw "overflow hidden link screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "overflow hidden link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($outPng) + try { + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + } + finally { + $bmp.Dispose() + } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $footerX = [int][Math]::Floor(($red.left + $red.right) / 2) + $footerY = [int][Math]::Floor(($red.top + $red.bottom) / 2) + Invoke-SmokeClientClick $hwnd $footerX $footerY | Out-Null + for ($i = 0; $i -lt 8; $i++) { + Start-Sleep -Milliseconds 200 + $titleAfterFooter = Get-SmokeWindowTitle $hwnd + if ($titleAfterFooter -like "Overflow Hidden Link Target*") { break } + } + $footerBlocked = -not ($titleAfterFooter -like "Overflow Hidden Link Target*") + if (-not $footerBlocked) { throw "overflow hidden footer click still activated the hidden link region" } + + $blueX = [int][Math]::Floor(($blue.left + $blue.right) / 2) + $blueY = [int][Math]::Floor(($blue.top + $blue.bottom) / 2) + Invoke-SmokeClientClick $hwnd $blueX $blueY | Out-Null + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfterBlue = Get-SmokeWindowTitle $hwnd + if ($titleAfterBlue -like "Overflow Hidden Link Target*") { + $visibleNavigated = $true + break + } + } + if (-not $visibleNavigated) { throw "visible clipped link region did not navigate" } +} +catch { + $failure = $_.Exception.Message +} +finally { + if ($browser) { Stop-VerifiedProcess $browser.Id } + if ($server) { Stop-VerifiedProcess $server.Id } + Start-Sleep -Milliseconds 200 + [ordered]@{ + title_before = $titleBefore + title_after_footer = $titleAfterFooter + title_after_blue = $titleAfterBlue + footer_blocked = $footerBlocked + visible_navigated = $visibleNavigated + blue = $blue + red = $red + error = $failure + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-probe.ps1 new file mode 100644 index 000000000..77d26c0e4 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-overflow-hidden-probe.ps1 @@ -0,0 +1,51 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8192 +$pageUrl = "http://127.0.0.1:$port/overflow-hidden.html" +$outPng = Join-Path $root "overflow-hidden.png" +$browserOut = Join-Path $root "overflow-hidden.browser.stdout.txt" +$browserErr = Join-Path $root "overflow-hidden.browser.stderr.txt" +$serverOut = Join-Path $root "overflow-hidden.server.stdout.txt" +$serverErr = Join-Path $root "overflow-hidden.server.stderr.txt" +$profileRoot = Join-Path $root "profile-overflow-hidden" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "overflow hidden smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "overflow hidden screenshot did not become ready" } + $blue = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 100 -and $c.B -le 100 } + $result = [ordered]@{ + blue = $blue + red = $red + blue_size_ok = ($blue.width -ge 120 -and $blue.width -le 122 -and $blue.height -ge 80 -and $blue.height -le 82) + red_size_ok = ($red.width -ge 120 -and $red.width -le 122 -and $red.height -ge 24 -and $red.height -le 26) + flow_ok = ($red.top -ge $blue.bottom + 14) + } + $result.overflow_hidden_worked = $result.blue_size_ok -and $result.red_size_ok -and $result.flow_ok + if (-not $result.overflow_hidden_worked) { throw "overflow hidden probe did not observe clipped block descendants and preserved later flow" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-layout-responsive-image-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-layout-responsive-image-probe.ps1 new file mode 100644 index 000000000..c0118a3a8 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-layout-responsive-image-probe.ps1 @@ -0,0 +1,78 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +function Find-ColorBoundsInRegion($Path, [scriptblock]$Predicate, [int]$MinX, [int]$MinY, [int]$MaxX, [int]$MaxY) { + Add-Type -AssemblyName System.Drawing + $bmp = [System.Drawing.Bitmap]::new($Path) + try { + $minFoundX = $bmp.Width + $minFoundY = $bmp.Height + $maxFoundX = -1 + $maxFoundY = -1 + $endX = [Math]::Min($MaxX, $bmp.Width - 1) + $endY = [Math]::Min($MaxY, $bmp.Height - 1) + for ($y = [Math]::Max(0, $MinY); $y -le $endY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $endX; $x++) { + $c = $bmp.GetPixel($x, $y) + if (& $Predicate $c) { + if ($x -lt $minFoundX) { $minFoundX = $x } + if ($y -lt $minFoundY) { $minFoundY = $y } + if ($x -gt $maxFoundX) { $maxFoundX = $x } + if ($y -gt $maxFoundY) { $maxFoundY = $y } + } + } + } + if ($maxFoundX -lt 0 -or $maxFoundY -lt 0) { + throw "target color not found in region for $Path" + } + return [ordered]@{ left=$minFoundX; top=$minFoundY; right=$maxFoundX; bottom=$maxFoundY; width=$maxFoundX-$minFoundX+1; height=$maxFoundY-$minFoundY+1 } + } + finally { $bmp.Dispose() } +} + +$port = 8190 +$pageUrl = "http://127.0.0.1:$port/responsive-image.html" +$outPng = Join-Path $root "responsive-image.png" +$browserOut = Join-Path $root "responsive-image.browser.stdout.txt" +$browserErr = Join-Path $root "responsive-image.browser.stderr.txt" +$serverOut = Join-Path $root "responsive-image.server.stdout.txt" +$serverErr = Join-Path $root "responsive-image.server.stderr.txt" +$profileRoot = Join-Path $root "profile-responsive-image" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","420","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + try { + if (-not (Wait-Screenshot $outPng)) { throw "responsive image screenshot did not become ready" } + $predicate = { param($c) $c.B -ge 180 -and $c.R -le 80 -and $c.G -ge 60 -and $c.G -le 150 } + $bounds = Find-ColorBoundsInRegion $outPng $predicate 20 80 240 400 + $result = [ordered]@{ + bounds = $bounds + width_ok = ($bounds.width -ge 119 -and $bounds.width -le 121) + height_ok = ($bounds.height -ge 239 -and $bounds.height -le 241) + } + $result.responsive_image_worked = $result.width_ok -and $result.height_ok + if (-not $result.responsive_image_worked) { throw "responsive image probe did not observe max-width shrinking with preserved aspect ratio" } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-screenshot-delayed-content-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-screenshot-delayed-content-probe.ps1 new file mode 100644 index 000000000..38793b007 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-screenshot-delayed-content-probe.ps1 @@ -0,0 +1,62 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/delayed-screenshot.html" +$outPng = Join-Path $root "delayed-screenshot.png" +$browserOut = Join-Path $root "delayed-screenshot.browser.stdout.txt" +$browserErr = Join-Path $root "delayed-screenshot.browser.stderr.txt" +$serverOut = Join-Path $root "delayed-screenshot.server.stdout.txt" +$serverErr = Join-Path $root "delayed-screenshot.server.stderr.txt" +$profileRoot = Join-Path $root "profile-delayed-screenshot" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $started = Get-Date + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","320","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "delayed screenshot did not become ready" } + $elapsedMs = [int]((Get-Date) - $started).TotalMilliseconds + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $result = [ordered]@{ + red = $red + elapsed_ms = $elapsedMs + delayed_content_visible = ($red.width -ge 160) -and ($red.height -ge 24) + capture_waited = ($elapsedMs -ge 500) + } + $result.delayed_screenshot_worked = $result.delayed_content_visible -and $result.capture_waited + if (-not $result.delayed_screenshot_worked) { + throw "delayed screenshot probe captured before delayed content was painted" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-screenshot-load-complete-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-screenshot-load-complete-probe.ps1 new file mode 100644 index 000000000..85eb0f312 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-screenshot-load-complete-probe.ps1 @@ -0,0 +1,62 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/load-complete-screenshot.html" +$outPng = Join-Path $root "load-complete-screenshot.png" +$browserOut = Join-Path $root "load-complete-screenshot.browser.stdout.txt" +$browserErr = Join-Path $root "load-complete-screenshot.browser.stderr.txt" +$serverOut = Join-Path $root "load-complete-screenshot.server.stdout.txt" +$serverErr = Join-Path $root "load-complete-screenshot.server.stderr.txt" +$profileRoot = Join-Path $root "profile-load-complete-screenshot" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $started = Get-Date + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","320","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "load-complete screenshot did not become ready" } + $elapsedMs = [int]((Get-Date) - $started).TotalMilliseconds + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 140 -and $c.B -le 140 } + $result = [ordered]@{ + red = $red + elapsed_ms = $elapsedMs + slow_image_visible = ($red.width -ge 70) -and ($red.height -ge 24) + waited_for_load = ($elapsedMs -ge 900) + } + $result.load_complete_screenshot_worked = $result.slow_image_visible -and $result.waited_for_load + if (-not $result.load_complete_screenshot_worked) { + throw "load-complete screenshot probe captured before slow load finished" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-screenshot-tiny-placeholder-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-screenshot-tiny-placeholder-probe.ps1 new file mode 100644 index 000000000..0deb04afc --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-screenshot-tiny-placeholder-probe.ps1 @@ -0,0 +1,62 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8183 +$pageUrl = "http://127.0.0.1:$port/tiny-placeholder-screenshot.html" +$outPng = Join-Path $root "tiny-placeholder-screenshot.png" +$browserOut = Join-Path $root "tiny-placeholder-screenshot.browser.stdout.txt" +$browserErr = Join-Path $root "tiny-placeholder-screenshot.browser.stderr.txt" +$serverOut = Join-Path $root "tiny-placeholder-screenshot.server.stdout.txt" +$serverErr = Join-Path $root "tiny-placeholder-screenshot.server.stderr.txt" +$profileRoot = Join-Path $root "profile-tiny-placeholder-screenshot" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $started = Get-Date + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","420","--window_height","320","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "tiny placeholder screenshot did not become ready" } + $elapsedMs = [int]((Get-Date) - $started).TotalMilliseconds + $green = Find-ColorBounds $outPng { param($c) $c.G -ge 140 -and $c.R -le 100 -and $c.B -le 140 } + $result = [ordered]@{ + green = $green + elapsed_ms = $elapsedMs + delayed_content_visible = ($green.width -ge 200) -and ($green.height -ge 32) + capture_waited = ($elapsedMs -ge 550) + } + $result.tiny_placeholder_screenshot_worked = $result.delayed_content_visible -and $result.capture_waited + if (-not $result.tiny_placeholder_screenshot_worked) { + throw "tiny placeholder screenshot probe captured before substantive content was painted" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-selector-forgiving-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-selector-forgiving-probe.ps1 new file mode 100644 index 000000000..df0f884ca --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-selector-forgiving-probe.ps1 @@ -0,0 +1,66 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8180 +$pageUrl = "http://127.0.0.1:$port/selector-forgiving.html" +$outPng = Join-Path $root "selector-forgiving.png" +$browserOut = Join-Path $root "selector-forgiving.browser.stdout.txt" +$browserErr = Join-Path $root "selector-forgiving.browser.stderr.txt" +$serverOut = Join-Path $root "selector-forgiving.server.stdout.txt" +$serverErr = Join-Path $root "selector-forgiving.server.stderr.txt" +$profileRoot = Join-Path $root "profile-selector-forgiving" + +Remove-Item $outPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--window_width","520","--window_height","320","--screenshot_png",$outPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + if (-not (Wait-Screenshot $outPng)) { throw "selector forgiving screenshot did not become ready" } + $red = Find-ColorBounds $outPng { param($c) $c.R -ge 180 -and $c.G -le 90 -and $c.B -le 90 } + $blueFound = $true + try { + $null = Find-ColorBounds $outPng { param($c) $c.B -ge 180 -and $c.R -le 90 -and $c.G -le 150 } + } catch { + $blueFound = $false + } + + $result = [ordered]@{ + red = $red + red_visible = $red.width -ge 160 + duplicate_hidden = -not $blueFound + } + $result.selector_forgiving_worked = $result.red_visible -and $result.duplicate_hidden + if (-not $result.selector_forgiving_worked) { + throw "selector forgiving probe did not preserve the valid branch while hiding the duplicate" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/chrome-selector-microtask-probe.ps1 b/tmp-browser-smoke/layout-smoke/chrome-selector-microtask-probe.ps1 new file mode 100644 index 000000000..534a6a244 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/chrome-selector-microtask-probe.ps1 @@ -0,0 +1,61 @@ +$ErrorActionPreference = "Stop" + +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\layout-smoke" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "layout_server.py" +$common = Join-Path $root "LayoutProbeCommon.ps1" +. $common + +$port = 8177 +$pageUrl = "http://127.0.0.1:$port/selector-microtask.html" +$browserOut = Join-Path $root "selector-microtask.browser.stdout.txt" +$browserErr = Join-Path $root "selector-microtask.browser.stderr.txt" +$serverOut = Join-Path $root "selector-microtask.server.stdout.txt" +$serverErr = Join-Path $root "selector-microtask.server.stderr.txt" +$profileRoot = Join-Path $root "profile-selector-microtask" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Reset-ProfileRoot $profileRoot + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + +try { + if (-not (Wait-HttpReady $pageUrl)) { throw "layout smoke server did not become ready" } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl,"--headed","--window_width","420","--window_height","320" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + try { + Start-Sleep -Seconds 3 + $alive = $null -ne (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) + $stderr = Get-Content $browserErr -Raw + $fatal = ($stderr -match "Fatal V8 Error") -or + ($stderr -match "InvalidPseudoClass") -or + ($stderr -match "HandleScope::CreateHandle") + $result = [ordered]@{ + alive_after_3s = $alive + fatal = $fatal + selector_microtask_worked = $alive -and (-not $fatal) + } + if (-not $result.selector_microtask_worked) { + throw "selector microtask probe observed browser death or fatal selector/v8 output" + } + $result | ConvertTo-Json -Depth 6 + } + finally { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } + } +} +finally { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 + } +} diff --git a/tmp-browser-smoke/layout-smoke/delayed-screenshot.html b/tmp-browser-smoke/layout-smoke/delayed-screenshot.html new file mode 100644 index 000000000..f107b6763 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/delayed-screenshot.html @@ -0,0 +1,33 @@ + + + + + Delayed Screenshot Layout + + + + + + diff --git a/tmp-browser-smoke/layout-smoke/fixed-position.html b/tmp-browser-smoke/layout-smoke/fixed-position.html new file mode 100644 index 000000000..6ce19e9fb --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/fixed-position.html @@ -0,0 +1,45 @@ + + + + + Fixed Position Smoke + + + +
+ + + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-align-content.html b/tmp-browser-smoke/layout-smoke/flex-align-content.html new file mode 100644 index 000000000..e572e5891 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-align-content.html @@ -0,0 +1,57 @@ + + + + + Flex Align Content - Lightpanda Browser + + + +
+
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-align-self.html b/tmp-browser-smoke/layout-smoke/flex-align-self.html new file mode 100644 index 000000000..09a3b70ed --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-align-self.html @@ -0,0 +1,68 @@ + + + + + Flex Align Self - Lightpanda Browser + + + +
+
+
+
+
+ + + + diff --git a/tmp-browser-smoke/layout-smoke/flex-center.html b/tmp-browser-smoke/layout-smoke/flex-center.html new file mode 100644 index 000000000..ed6ec9329 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-center.html @@ -0,0 +1,46 @@ + + + + + Flex Center Layout + + + +
+ + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-grow.html b/tmp-browser-smoke/layout-smoke/flex-grow.html new file mode 100644 index 000000000..484879353 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-grow.html @@ -0,0 +1,60 @@ + + + + + Flex Grow Layout + + + +
+ + + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-order.html b/tmp-browser-smoke/layout-smoke/flex-order.html new file mode 100644 index 000000000..dafbc7c37 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-order.html @@ -0,0 +1,63 @@ + + + + + Flex Order - Lightpanda Browser + + + +
+ RED + BLUE + GREEN +
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-overflow-hidden.html b/tmp-browser-smoke/layout-smoke/flex-overflow-hidden.html new file mode 100644 index 000000000..b6f371f94 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-overflow-hidden.html @@ -0,0 +1,9 @@ + + + +
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-row-wrap.html b/tmp-browser-smoke/layout-smoke/flex-row-wrap.html new file mode 100644 index 000000000..601dbc7d1 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-row-wrap.html @@ -0,0 +1,51 @@ + + + + + Flex Row Wrap Layout + + + +
+ + + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/flex-shrink.html b/tmp-browser-smoke/layout-smoke/flex-shrink.html new file mode 100644 index 000000000..e331d7281 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex-shrink.html @@ -0,0 +1,66 @@ + + + + + Flex Shrink - Lightpanda Browser + + + +
+
+
+
+
+ + + + diff --git a/tmp-browser-smoke/layout-smoke/flex_order_blue_target.html b/tmp-browser-smoke/layout-smoke/flex_order_blue_target.html new file mode 100644 index 000000000..7c32f6baa --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex_order_blue_target.html @@ -0,0 +1,19 @@ + + + + + Flex Order Blue Target + + + + Blue target + + diff --git a/tmp-browser-smoke/layout-smoke/flex_order_green_target.html b/tmp-browser-smoke/layout-smoke/flex_order_green_target.html new file mode 100644 index 000000000..a6b2472c5 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex_order_green_target.html @@ -0,0 +1,19 @@ + + + + + Flex Order Green Target + + + + Green target + + diff --git a/tmp-browser-smoke/layout-smoke/flex_order_red_target.html b/tmp-browser-smoke/layout-smoke/flex_order_red_target.html new file mode 100644 index 000000000..fd2df36a1 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/flex_order_red_target.html @@ -0,0 +1,19 @@ + + + + + Flex Order Red Target + + + + Red target + + diff --git a/tmp-browser-smoke/layout-smoke/float-dock.html b/tmp-browser-smoke/layout-smoke/float-dock.html new file mode 100644 index 000000000..a52ecacd1 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/float-dock.html @@ -0,0 +1,51 @@ + + + + + + +
+
Left Dock
+
Right Dock
+
Body Flow
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/intrinsic-image.html b/tmp-browser-smoke/layout-smoke/intrinsic-image.html new file mode 100644 index 000000000..b3940cbbd --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/intrinsic-image.html @@ -0,0 +1,9 @@ + + +Intrinsic Image Layout - Lightpanda Browser + + + + + + diff --git a/tmp-browser-smoke/layout-smoke/layout-tall-blue.png b/tmp-browser-smoke/layout-smoke/layout-tall-blue.png new file mode 100644 index 000000000..fbf7ad9db Binary files /dev/null and b/tmp-browser-smoke/layout-smoke/layout-tall-blue.png differ diff --git a/tmp-browser-smoke/layout-smoke/layout_server.py b/tmp-browser-smoke/layout-smoke/layout_server.py new file mode 100644 index 000000000..b61cc38aa --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/layout_server.py @@ -0,0 +1,64 @@ +import http.server +import socketserver +import sys +import time +import struct +import zlib +from functools import partial +from pathlib import Path + + +def png_chunk(tag: bytes, data: bytes) -> bytes: + return ( + struct.pack("!I", len(data)) + + tag + + data + + struct.pack("!I", zlib.crc32(tag + data) & 0xFFFFFFFF) + ) + + +def make_red_png() -> bytes: + width = 1 + height = 1 + raw_scanlines = b"\x00" + bytes((220, 50, 50, 255)) + return ( + b"\x89PNG\r\n\x1a\n" + + png_chunk(b"IHDR", struct.pack("!IIBBBBB", width, height, 8, 6, 0, 0, 0)) + + png_chunk(b"IDAT", zlib.compress(raw_scanlines)) + + png_chunk(b"IEND", b"") + ) + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: layout_server.py ", file=sys.stderr) + return 2 + + port = int(sys.argv[1]) + root = Path(__file__).resolve().parent + red_png = make_red_png() + + class LayoutHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(root), **kwargs) + + def do_GET(self): + if self.path == "/slow-ready.png": + time.sleep(1.0) + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(red_png))) + self.end_headers() + self.wfile.write(red_png) + return + return super().do_GET() + + class ReuseServer(socketserver.ThreadingTCPServer): + allow_reuse_address = True + + with ReuseServer(("127.0.0.1", port), LayoutHandler) as httpd: + httpd.serve_forever() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/layout-smoke/legacy-table.html b/tmp-browser-smoke/layout-smoke/legacy-table.html new file mode 100644 index 000000000..a1a38b1ff --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/legacy-table.html @@ -0,0 +1,89 @@ + + + + + + +
+ +
+ + + + + + +
  +
+
+ + +
+
Advanced
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/load-complete-screenshot.html b/tmp-browser-smoke/layout-smoke/load-complete-screenshot.html new file mode 100644 index 000000000..2525c4a26 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/load-complete-screenshot.html @@ -0,0 +1,33 @@ + + + + + Load Complete Screenshot Pending + + + +
WAITING
+ + + diff --git a/tmp-browser-smoke/layout-smoke/min-max-height.html b/tmp-browser-smoke/layout-smoke/min-max-height.html new file mode 100644 index 000000000..2143f0993 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/min-max-height.html @@ -0,0 +1,10 @@ + + + +
+
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/next-overflow-link.html b/tmp-browser-smoke/layout-smoke/next-overflow-link.html new file mode 100644 index 000000000..19f1e0cff --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/next-overflow-link.html @@ -0,0 +1,9 @@ + + + + Overflow Hidden Link Target - Lightpanda Browser + + + Overflow link target + + diff --git a/tmp-browser-smoke/layout-smoke/overflow-hidden-link.html b/tmp-browser-smoke/layout-smoke/overflow-hidden-link.html new file mode 100644 index 000000000..169cd50ec --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/overflow-hidden-link.html @@ -0,0 +1,12 @@ + + + + Overflow Hidden Link Layout - Lightpanda Browser + + + +
+ + diff --git a/tmp-browser-smoke/layout-smoke/overflow-hidden.html b/tmp-browser-smoke/layout-smoke/overflow-hidden.html new file mode 100644 index 000000000..a5f2eea19 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/overflow-hidden.html @@ -0,0 +1,9 @@ + + + +
+
+
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/responsive-image.html b/tmp-browser-smoke/layout-smoke/responsive-image.html new file mode 100644 index 000000000..d5aef71b3 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/responsive-image.html @@ -0,0 +1,9 @@ + + +Responsive Image Layout - Lightpanda Browser + +
+ +
+ + diff --git a/tmp-browser-smoke/layout-smoke/selector-forgiving.html b/tmp-browser-smoke/layout-smoke/selector-forgiving.html new file mode 100644 index 000000000..3c2217819 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/selector-forgiving.html @@ -0,0 +1,23 @@ + + + + + Selector Forgiving Layout + + + +
+
+ + diff --git a/tmp-browser-smoke/layout-smoke/selector-microtask.html b/tmp-browser-smoke/layout-smoke/selector-microtask.html new file mode 100644 index 000000000..4d8ee8b65 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/selector-microtask.html @@ -0,0 +1,22 @@ + + + + Selector Microtask Pending + + + + + diff --git a/tmp-browser-smoke/layout-smoke/tiny-placeholder-screenshot.html b/tmp-browser-smoke/layout-smoke/tiny-placeholder-screenshot.html new file mode 100644 index 000000000..00356c650 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/tiny-placeholder-screenshot.html @@ -0,0 +1,22 @@ + + + + + Tiny Placeholder Screenshot + + + + + + + diff --git a/tmp-browser-smoke/layout-smoke/zindex-high.html b/tmp-browser-smoke/layout-smoke/zindex-high.html new file mode 100644 index 000000000..5f8383a5d --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/zindex-high.html @@ -0,0 +1,8 @@ + + + + + Z Index High Target + +high + diff --git a/tmp-browser-smoke/layout-smoke/zindex-low.html b/tmp-browser-smoke/layout-smoke/zindex-low.html new file mode 100644 index 000000000..4431e8455 --- /dev/null +++ b/tmp-browser-smoke/layout-smoke/zindex-low.html @@ -0,0 +1,8 @@ + + + + + Z Index Low Target + +low + diff --git a/tmp-browser-smoke/links.html b/tmp-browser-smoke/links.html new file mode 100644 index 000000000..75e20128f --- /dev/null +++ b/tmp-browser-smoke/links.html @@ -0,0 +1,20 @@ + + + + + Lightpanda Link Smoke + + + +
+

Smoke Page

+

Renderer should show a bordered box and an underlined link.

+ Nested headed link + +
+ + diff --git a/tmp-browser-smoke/localstorage-persistence/StorageProbeCommon.ps1 b/tmp-browser-smoke/localstorage-persistence/StorageProbeCommon.ps1 new file mode 100644 index 000000000..2cf432856 --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/StorageProbeCommon.ps1 @@ -0,0 +1,147 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\localstorage-persistence" +$script:BrowserExe = Join-Path $script:Repo "zig-out\bin\lightpanda.exe" + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-StorageProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + LocalStorageFile = Join-Path $appDataRoot "local-storage-v1.txt" + SettingsFile = Join-Path $appDataRoot "browse-settings-v1.txt" + } +} + +function Seed-StorageProfile([string]$AppDataRoot) { +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Wait-StorageServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/seed.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-StorageServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "storage_server.py"),"$Port" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Start-StorageBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Invoke-StorageAddressCommit([IntPtr]$Hwnd, [string]$Url) { + [void](Invoke-SmokeClientClick $Hwnd 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 120 + Send-SmokeText $Url + Start-Sleep -Milliseconds 120 + Send-SmokeEnter +} + +function Invoke-StorageAddressNavigate([IntPtr]$Hwnd, [int]$BrowserId, [string]$Url, [string]$Needle) { + Invoke-StorageAddressCommit $Hwnd $Url + return Wait-TabTitle $BrowserId $Needle 40 +} + +function Read-LocalStorageFileData([string]$LocalStorageFile) { + if (-not (Test-Path $LocalStorageFile)) { + return "" + } + return Get-Content $LocalStorageFile -Raw +} + +function ConvertTo-LocalStorageEntryPattern([string]$Origin, [string]$Key, [string]$Value) { + $originField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Origin)) + $keyField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Key)) + $valueField = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Value)) + return [regex]::Escape("entry`t$originField`t$keyField`t$valueField") +} + +function Wait-LocalStorageFileMatch([string]$LocalStorageFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-LocalStorageFileData $LocalStorageFile + if ($data -match $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Wait-LocalStorageFileNoMatch([string]$LocalStorageFile, [string]$Pattern, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + $data = Read-LocalStorageFileData $LocalStorageFile + if ($data -notmatch $Pattern) { + return $data + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +function Wait-OwnedProbeProcessGone([int]$ProcessId, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + if (-not (Get-Process -Id $ProcessId -ErrorAction SilentlyContinue)) { + return $true + } + Start-Sleep -Milliseconds 150 + } + return $false +} + +function Format-StorageProbeProcessMeta($Meta) { + if (-not $Meta) { + return $null + } + + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-StorageProbeResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + + if ($value -is [System.Collections.IDictionary]) { + Write-StorageProbeResult $value "$key." + continue + } + + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-clear-probe.ps1 b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-clear-probe.ps1 new file mode 100644 index 000000000..64b7aa1ee --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-clear-probe.ps1 @@ -0,0 +1,120 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\localstorage-persistence\StorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-localstorage-clear" +$app = Reset-StorageProfile $profileRoot +Seed-StorageProfile $app.AppDataRoot +$port = 8202 +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-LocalStorageEntryPattern $origin "lppersist" "ok" +$browserOneOut = Join-Path $Root "chrome-localstorage-clear.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-localstorage-clear.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-localstorage-clear.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-localstorage-clear.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-localstorage-clear.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-localstorage-clear.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$settingsOpened = $false +$clearInvoked = $false +$clearedOnDisk = $false +$missingAfterClear = $false +$missingAfterRestart = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$storageData = "" + +try { + $server = Start-StorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-StorageServer -Port $port + if (-not $ready) { throw "localstorage server did not become ready" } + + $browserOne = Start-StorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "localstorage clear run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "Local Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish localStorage write" } + + $storageData = Wait-LocalStorageFileMatch $app.LocalStorageFile $entryPattern + if (-not $storageData) { throw "localStorage data did not persist before clear" } + + $titles.settings = Invoke-StorageAddressNavigate $hwndOne $browserOne.Id "browser://settings" "Browser Settings" + $settingsOpened = [bool]$titles.settings + if (-not $settingsOpened) { throw "browser://settings did not load" } + + $titles.settings_after_clear = Invoke-StorageAddressNavigate $hwndOne $browserOne.Id "browser://settings/clear-local-storage" "Browser Settings" + $clearInvoked = [bool]$titles.settings_after_clear + if (-not $clearInvoked) { throw "clear localStorage action did not return to settings page" } + + $storageData = Wait-LocalStorageFileNoMatch $app.LocalStorageFile $entryPattern + $clearedOnDisk = [bool]$storageData + if (-not $clearedOnDisk) { throw "localStorage persisted file still contains cleared entry" } + + $titles.echo_missing = Invoke-StorageAddressNavigate $hwndOne $browserOne.Id "$origin/echo.html" "Local Storage Echo missing" + $missingAfterClear = [bool]$titles.echo_missing + if (-not $missingAfterClear) { throw "localStorage remained visible after clear" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-StorageBrowser -StartupUrl "$origin/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "localstorage clear run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart_missing = Wait-TabTitle $browserTwo.Id "Local Storage Echo missing" 40 + $missingAfterRestart = [bool]$titles.restart_missing + if (-not $missingAfterRestart) { throw "localStorage remained present after restart" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $storageData) { $storageData = Read-LocalStorageFileData $app.LocalStorageFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + settings_opened = $settingsOpened + clear_invoked = $clearInvoked + cleared_on_disk = $clearedOnDisk + missing_after_clear = $missingAfterClear + missing_after_restart = $missingAfterRestart + titles = $titles + local_storage_file = $storageData + error = $failure + server_meta = Format-StorageProbeProcessMeta $serverMeta + browser_one_meta = Format-StorageProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-StorageProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-StorageProbeResult $result + + if ($failure -or -not $seedWorked -or -not $settingsOpened -or -not $clearInvoked -or -not $clearedOnDisk -or -not $missingAfterClear -or -not $missingAfterRestart) { + exit 1 + } +} diff --git a/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-cross-tab-probe.ps1 b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-cross-tab-probe.ps1 new file mode 100644 index 000000000..0801a331c --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-cross-tab-probe.ps1 @@ -0,0 +1,85 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\localstorage-persistence\StorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-localstorage-cross-tab" +$app = Reset-StorageProfile $profileRoot +Seed-StorageProfile $app.AppDataRoot +$port = 8200 +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-LocalStorageEntryPattern $origin "lppersist" "ok" +$browserOut = Join-Path $Root "chrome-localstorage-cross-tab.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-localstorage-cross-tab.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-localstorage-cross-tab.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-localstorage-cross-tab.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$persistedToDisk = $false +$echoWorked = $false +$seedTabRetained = $false +$failure = $null +$titles = [ordered]@{} +$storageData = "" + +try { + $server = Start-StorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-StorageServer -Port $port + if (-not $ready) { throw "localstorage server did not become ready" } + + $browser = Start-StorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "localstorage cross-tab window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-TabTitle $browser.Id "Local Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish localStorage write" } + + $storageData = Wait-LocalStorageFileMatch $app.LocalStorageFile $entryPattern + $persistedToDisk = [bool]$storageData + if (-not $persistedToDisk) { throw "localStorage data did not persist to disk before cross-tab check" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-StorageAddressNavigate $hwnd $browser.Id "$origin/echo.html" "Local Storage Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "new tab did not see shared localStorage" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-TabTitle $browser.Id "Local Storage Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "seed tab was not preserved after cross-tab check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + persisted_to_disk = $persistedToDisk + echo_worked = $echoWorked + seed_tab_retained = $seedTabRetained + local_storage_file = if ($storageData) { $storageData } else { Read-LocalStorageFileData $app.LocalStorageFile } + titles = $titles + error = $failure + server_meta = Format-StorageProbeProcessMeta $serverMeta + browser_meta = Format-StorageProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-StorageProbeResult $result + + if ($failure -or -not $seedWorked -or -not $persistedToDisk -or -not $echoWorked -or -not $seedTabRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-restart-probe.ps1 b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-restart-probe.ps1 new file mode 100644 index 000000000..3cf676c3a --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-restart-probe.ps1 @@ -0,0 +1,99 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\localstorage-persistence\StorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-localstorage-restart" +$app = Reset-StorageProfile $profileRoot +Seed-StorageProfile $app.AppDataRoot +$port = 8201 +$origin = "http://127.0.0.1:$port" +$entryPattern = ConvertTo-LocalStorageEntryPattern $origin "lppersist" "ok" +$browserOneOut = Join-Path $Root "chrome-localstorage-restart.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-localstorage-restart.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-localstorage-restart.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-localstorage-restart.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-localstorage-restart.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-localstorage-restart.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$persistedToDisk = $false +$restartWorked = $false +$browserOneGoneBeforeRestart = $false +$failure = $null +$titles = [ordered]@{} +$storageData = "" + +try { + $server = Start-StorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-StorageServer -Port $port + if (-not $ready) { throw "localstorage server did not become ready" } + + $browserOne = Start-StorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "localstorage restart run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-TabTitle $browserOne.Id "Local Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish localStorage write" } + + $storageData = Wait-LocalStorageFileMatch $app.LocalStorageFile $entryPattern + $persistedToDisk = [bool]$storageData + if (-not $persistedToDisk) { throw "localStorage data was not persisted before restart" } + + $browserOneMeta = Stop-OwnedProbeProcess $browserOne + $browserOneGoneBeforeRestart = Wait-OwnedProbeProcessGone $browserOne.Id + $browserOne = $null + if (-not $browserOneGoneBeforeRestart) { throw "run1 browser pid did not exit before restart" } + Start-Sleep -Milliseconds 300 + + $browserTwo = Start-StorageBrowser -StartupUrl "$origin/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "localstorage restart run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart = Wait-TabTitle $browserTwo.Id "Local Storage Echo ok" 40 + $restartWorked = [bool]$titles.restart + if (-not $restartWorked) { throw "restarted browser did not reuse persisted localStorage" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMetaFinal = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = Stop-OwnedProbeProcess $browserTwo + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + if (-not $storageData) { $storageData = Read-LocalStorageFileData $app.LocalStorageFile } + $browserOneMetaValue = if ($browserOneMeta) { $browserOneMeta } else { $browserOneMetaFinal } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + persisted_to_disk = $persistedToDisk + restart_worked = $restartWorked + titles = $titles + local_storage_file = $storageData + error = $failure + server_meta = Format-StorageProbeProcessMeta $serverMeta + browser_one_meta = Format-StorageProbeProcessMeta $browserOneMetaValue + browser_two_meta = Format-StorageProbeProcessMeta $browserTwoMeta + browser_one_gone_before_restart = $browserOneGoneBeforeRestart + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-StorageProbeResult $result + + if ($failure -or -not $seedWorked -or -not $persistedToDisk -or -not $restartWorked) { + exit 1 + } +} diff --git a/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-storage-event-probe.ps1 b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-storage-event-probe.ps1 new file mode 100644 index 000000000..dcd78182a --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/chrome-localstorage-storage-event-probe.ps1 @@ -0,0 +1,83 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\localstorage-persistence\StorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-localstorage-storage-event" +$app = Reset-StorageProfile $profileRoot +Seed-StorageProfile $app.AppDataRoot +$port = 8321 +$origin = "http://127.0.0.1:$port" +$browserOut = Join-Path $Root "chrome-localstorage-storage-event.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-localstorage-storage-event.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-localstorage-storage-event.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-localstorage-storage-event.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$listenerReady = $false +$writerWorked = $false +$listenerReceived = $false +$listenerRetained = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-StorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-StorageServer -Port $port + if (-not $ready) { throw "localstorage server did not become ready" } + + $browser = Start-StorageBrowser -StartupUrl "$origin/listener.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "localstorage event window handle not found" } + Show-SmokeWindow $hwnd + + $titles.listener_ready = Wait-TabTitle $browser.Id "Local Storage Listener Ready" 40 + $listenerReady = [bool]$titles.listener_ready + if (-not $listenerReady) { throw "listener page did not become ready" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.writer = Invoke-StorageAddressNavigate $hwnd $browser.Id "$origin/writer.html" "Local Storage Writer Wrote" + $writerWorked = [bool]$titles.writer + if (-not $writerWorked) { throw "writer page did not write localStorage" } + + Send-SmokeCtrlShiftTab + $titles.back_to_listener = Wait-TabTitle $browser.Id "Local Storage Event ok" 40 + $listenerReceived = [bool]$titles.back_to_listener + if (-not $listenerReceived) { throw "listener tab did not receive storage event" } + + Send-SmokeCtrlTab + $titles.return_to_writer = Wait-TabTitle $browser.Id "Local Storage Writer Wrote" 20 + $listenerRetained = [bool]$titles.return_to_writer + if (-not $listenerRetained) { throw "writer tab was not preserved after storage event delivery" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + listener_ready = $listenerReady + writer_worked = $writerWorked + listener_received = $listenerReceived + writer_tab_retained = $listenerRetained + titles = $titles + error = $failure + server_meta = Format-StorageProbeProcessMeta $serverMeta + browser_meta = Format-StorageProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-StorageProbeResult $result + + if ($failure -or -not $listenerReady -or -not $writerWorked -or -not $listenerReceived -or -not $listenerRetained) { + exit 1 + } +} diff --git a/tmp-browser-smoke/localstorage-persistence/storage_server.py b/tmp-browser-smoke/localstorage-persistence/storage_server.py new file mode 100644 index 000000000..ea4088835 --- /dev/null +++ b/tmp-browser-smoke/localstorage-persistence/storage_server.py @@ -0,0 +1,105 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys + + +def html(title: str, body: str) -> bytes: + return ( + "" + f"{title}{body}" + ).encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + server_version = "LocalStorageSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def do_GET(self): + if self.path == "/favicon.ico": + self.send_response(204) + self.end_headers() + return + + if self.path == "/seed.html": + body = html( + "Local Storage Loading - Lightpanda Browser", + "

seed

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/echo.html": + body = html( + "Local Storage Loading - Lightpanda Browser", + "

echo

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/listener.html": + body = html( + "Local Storage Listener Ready - Lightpanda Browser", + "

listener

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/writer.html": + body = html( + "Local Storage Writer Loading - Lightpanda Browser", + "

writer

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = html("Not Found - Lightpanda Browser", "

Not Found

") + self.send_response(404) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + +def main() -> int: + port = 8200 + if len(sys.argv) > 1: + port = int(sys.argv[1]) + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/multi-image/green.png b/tmp-browser-smoke/multi-image/green.png new file mode 100644 index 000000000..1a5db7393 Binary files /dev/null and b/tmp-browser-smoke/multi-image/green.png differ diff --git a/tmp-browser-smoke/multi-image/index.html b/tmp-browser-smoke/multi-image/index.html new file mode 100644 index 000000000..2d08bc514 --- /dev/null +++ b/tmp-browser-smoke/multi-image/index.html @@ -0,0 +1,15 @@ + + + + + Multi Image Smoke + + + + red data + green file + + diff --git a/tmp-browser-smoke/multi-image/red.png b/tmp-browser-smoke/multi-image/red.png new file mode 100644 index 000000000..1bc71ca05 Binary files /dev/null and b/tmp-browser-smoke/multi-image/red.png differ diff --git a/tmp-browser-smoke/next.html b/tmp-browser-smoke/next.html new file mode 100644 index 000000000..44540ae1f --- /dev/null +++ b/tmp-browser-smoke/next.html @@ -0,0 +1 @@ +next \ No newline at end of file diff --git a/tmp-browser-smoke/page1.html b/tmp-browser-smoke/page1.html new file mode 100644 index 000000000..e34d7745a --- /dev/null +++ b/tmp-browser-smoke/page1.html @@ -0,0 +1,3 @@ +Page One + +Open second page \ No newline at end of file diff --git a/tmp-browser-smoke/png-page.html b/tmp-browser-smoke/png-page.html new file mode 100644 index 000000000..48efb7051 --- /dev/null +++ b/tmp-browser-smoke/png-page.html @@ -0,0 +1,19 @@ + + + + + PNG Smoke + + + +
+

PNG Smoke

+

Headed browse should emit a PNG file.

+ Open link +
+ + diff --git a/tmp-browser-smoke/popup/anchor-index.html b/tmp-browser-smoke/popup/anchor-index.html new file mode 100644 index 000000000..4df4a3ec1 --- /dev/null +++ b/tmp-browser-smoke/popup/anchor-index.html @@ -0,0 +1,12 @@ + + + + + Popup Anchor Start + + +
+ OPEN POPUP TAB +
+ + diff --git a/tmp-browser-smoke/popup/anchor-result.html b/tmp-browser-smoke/popup/anchor-result.html new file mode 100644 index 000000000..9435c80ce --- /dev/null +++ b/tmp-browser-smoke/popup/anchor-result.html @@ -0,0 +1,12 @@ + + + + + Popup Anchor Result + + +
+

Popup Anchor Result

+
+ + diff --git a/tmp-browser-smoke/popup/chrome-popup-anchor-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-anchor-probe.ps1 new file mode 100644 index 000000000..b63e46cc7 --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-anchor-probe.ps1 @@ -0,0 +1,75 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-anchor" +$port = 8158 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-anchor.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-anchor.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-anchor.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-anchor.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$anchorWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/anchor-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "popup anchor probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/anchor-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "popup anchor probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Anchor Start" + if (-not $titles.initial) { throw "popup anchor probe initial title missing" } + + [void](Invoke-SmokeClientClick $hwnd 120 138) + $titles.result = Wait-TabTitle $browser.Id "Popup Anchor Result" + $anchorWorked = [bool]$titles.result + if (-not $anchorWorked) { throw "popup anchor probe did not open result tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + anchor_worked = $anchorWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-background-timer-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-background-timer-probe.ps1 new file mode 100644 index 000000000..a6ecc864a --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-background-timer-probe.ps1 @@ -0,0 +1,91 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-background-timer" +$port = 8177 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-background-timer.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-background-timer.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-background-timer.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-background-timer.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$popupWorked = $false +$backgroundWorked = $false +$serverSawPopup = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/script-popup-background-timer.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "popup background timer server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/script-popup-background-timer.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "popup background timer window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Background Timer Start" 6 + + Start-Sleep -Milliseconds 900 + Send-SmokeCtrlDigit 2 + $titles.popup = Wait-TabTitle $browser.Id "Popup Script Blank Result" 24 + $popupWorked = [bool]$titles.popup + if (-not $popupWorked) { throw "popup background timer did not open popup tab" } + + Send-SmokeCtrlDigit 1 + $titles.background = Wait-TabTitle $browser.Id "Popup Background Timer Fired" 24 + $backgroundWorked = [bool]$titles.background + if (-not $backgroundWorked) { throw "popup background timer did not fire launcher callback after popup open" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawPopup = $serverLog -match 'GET /script-popup-blank-result\.html' + } + if (-not $failure -and -not $serverSawPopup) { + $failure = "server did not observe popup background timer result request" + } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + popup_worked = $popupWorked + background_worked = $backgroundWorked + server_saw_popup = $serverSawPopup + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-form-enter-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-form-enter-probe.ps1 new file mode 100644 index 000000000..a8f7a3c07 --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-form-enter-probe.ps1 @@ -0,0 +1,75 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-form-enter-official" +$port = 8166 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-form-enter.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-form-enter.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-form-enter.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-form-enter.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$formWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/form-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "popup form enter probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/form-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "popup form enter probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Form Start" + if (-not $titles.initial) { throw "popup form enter probe initial title missing" } + + Send-SmokeEnter + $titles.result = Wait-TabTitle $browser.Id "Popup Form Result" + $formWorked = [bool]$titles.result + if (-not $formWorked) { throw "popup form enter probe did not open result tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + form_worked = $formWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-form-post-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-form-post-probe.ps1 new file mode 100644 index 000000000..278c0b89c --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-form-post-probe.ps1 @@ -0,0 +1,83 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-form-post-official" +$port = 8167 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "popup_form_server.py" +$browserOut = Join-Path $root "chrome-popup-form-post.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-form-post.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-form-post.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-form-post.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$formWorked = $false +$serverSawPost = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/form-post-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "popup form post probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/form-post-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "popup form post probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Form Post Start" + if (-not $titles.initial) { throw "popup form post probe initial title missing" } + + Send-SmokeSpace + $titles.result = Wait-TabTitle $browser.Id "Popup Form Post Result popup-post" + $formWorked = [bool]$titles.result + if (-not $formWorked) { throw "popup form post probe did not open result tab" } + + if (Test-Path $serverErr) { + $serverSawPost = Select-String -Path $serverErr -Pattern "POPUP_POST_BODY q=popup-post" -Quiet + } + if (-not $serverSawPost) { throw "popup form post probe server did not record POST body" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + form_worked = $formWorked + server_saw_post = $serverSawPost + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-form-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-form-probe.ps1 new file mode 100644 index 000000000..9f0a7d7bf --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-form-probe.ps1 @@ -0,0 +1,78 @@ +# Acceptance probe: form-driven target=_blank should open a headed popup tab +# without crashing the Windows browser shell. +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-form" +$port = 8159 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-form.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-form.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-form.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-form.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$formWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/form-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "popup form probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/form-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "popup form probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Form Start" + if (-not $titles.initial) { throw "popup form probe initial title missing" } + + Start-Sleep -Milliseconds 200 + Send-SmokeSpace + $titles.result = Wait-TabTitle $browser.Id "Popup Form Result" + $formWorked = [bool]$titles.result + if (-not $formWorked) { throw "popup form probe did not open result tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + form_worked = $formWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-named-anchor-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-named-anchor-probe.ps1 new file mode 100644 index 000000000..ede05be3e --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-named-anchor-probe.ps1 @@ -0,0 +1,174 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-named-anchor" +$port = 8168 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-named-anchor.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-named-anchor.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-named-anchor.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-named-anchor.server.stderr.txt" +$screenshotPath = Join-Path $root "chrome-popup-named-anchor.png" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$screenshotPath -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +Add-Type -AssemblyName System.Drawing + +function Get-ColorBounds( + [string]$Path, + [scriptblock]$Predicate, + [int]$MinX = 0, + [int]$MinY = 0, + [int]$MaxX = -1, + [int]$MaxY = -1 +) { + $bitmap = [System.Drawing.Bitmap]::FromFile($Path) + try { + $foundMinX = 99999 + $foundMinY = 99999 + $foundMaxX = -1 + $foundMaxY = -1 + $scanMaxX = if ($MaxX -ge 0) { [Math]::Min($MaxX, $bitmap.Width - 1) } else { $bitmap.Width - 1 } + $scanMaxY = if ($MaxY -ge 0) { [Math]::Min($MaxY, $bitmap.Height - 1) } else { $bitmap.Height - 1 } + for ($y = [Math]::Max(0, $MinY); $y -le $scanMaxY; $y++) { + for ($x = [Math]::Max(0, $MinX); $x -le $scanMaxX; $x++) { + $color = $bitmap.GetPixel($x, $y) + if (& $Predicate $color) { + if ($x -lt $foundMinX) { $foundMinX = $x } + if ($y -lt $foundMinY) { $foundMinY = $y } + if ($x -gt $foundMaxX) { $foundMaxX = $x } + if ($y -gt $foundMaxY) { $foundMaxY = $y } + } + } + } + if ($foundMaxX -lt $foundMinX -or $foundMaxY -lt $foundMinY) { return $null } + return [ordered]@{ + min_x = $foundMinX + min_y = $foundMinY + max_x = $foundMaxX + max_y = $foundMaxY + click_x = [int][Math]::Floor(($foundMinX + $foundMaxX) / 2) + click_y = [int][Math]::Floor(($foundMinY + $foundMaxY) / 2) + } + } finally { + $bitmap.Dispose() + } +} + +$server = $null +$browser = $null +$ready = $false +$firstWorked = $false +$secondWorked = $false +$reusedTargetWorked = $false +$failure = $null +$titles = [ordered]@{} +$firstTargetBounds = $null +$secondTargetBounds = $null +$serverSawOne = $false +$serverSawTwo = $false + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/named-target-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "named target anchor server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/named-target-index.html","--window_width","960","--window_height","640","--screenshot_png",$screenshotPath -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "named target anchor window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Named Anchor Start" + if (-not $titles.initial) { throw "named target anchor initial title missing" } + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $screenshotPath) { break } + } + if (-not (Test-Path $screenshotPath)) { throw "named target anchor screenshot missing" } + + $firstTargetBounds = Get-ColorBounds $screenshotPath { param($c) $c.R -gt 180 -and $c.B -gt 80 -and $c.G -lt 100 } 20 100 + if (-not $firstTargetBounds) { throw "named target anchor first target bounds missing" } + $secondTargetBounds = Get-ColorBounds $screenshotPath { param($c) $c.R -gt 180 -and $c.G -gt 100 -and $c.B -lt 80 } 20 180 + if (-not $secondTargetBounds) { throw "named target anchor second target bounds missing" } + + [void](Invoke-SmokeClientClick $hwnd $firstTargetBounds.click_x $firstTargetBounds.click_y) + $titles.first = Wait-TabTitle $browser.Id "Popup Named Anchor Result One" + $firstWorked = [bool]$titles.first + if (-not $firstWorked) { throw "first named target anchor click did not open result one" } + + $sourceTabPoint = Get-TabClientPoint -TabIndex 0 -TabCount 2 + [void](Invoke-SmokeClientClick $hwnd $sourceTabPoint.X $sourceTabPoint.Y) + $titles.returned = Wait-TabTitle $browser.Id "Popup Named Anchor Start" + if (-not $titles.returned) { throw "named target probe did not return to launcher tab" } + + [void](Invoke-SmokeClientClick $hwnd $secondTargetBounds.click_x $secondTargetBounds.click_y) + $titles.second = Wait-TabTitle $browser.Id "Popup Named Anchor Result Two" + $secondWorked = [bool]$titles.second + if (-not $secondWorked) { throw "second named target anchor click did not open result two" } + + [void](Invoke-SmokeClientClick $hwnd $sourceTabPoint.X $sourceTabPoint.Y) + $titles.returned_again = Wait-TabTitle $browser.Id "Popup Named Anchor Start" + if (-not $titles.returned_again) { throw "named target probe did not return to launcher tab after second click" } + + Send-SmokeCtrlTab + $titles.reused = Wait-TabTitle $browser.Id "Popup Named Anchor Result Two" + $reusedTargetWorked = [bool]$titles.reused + if (-not $reusedTargetWorked) { throw "named target popup tab was not reused on second click" } + +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawOne = $serverLog -match 'GET /named-target-one\.html' + $serverSawTwo = $serverLog -match 'GET /named-target-two\.html' + } + if (-not $failure) { + if (-not $serverSawOne) { + $failure = "server did not observe named-target-one request" + } elseif (-not $serverSawTwo) { + $failure = "server did not observe named-target-two request" + } + } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + first_worked = $firstWorked + second_worked = $secondWorked + reused_target_worked = $reusedTargetWorked + server_saw_one = $serverSawOne + server_saw_two = $serverSawTwo + first_target_bounds = $firstTargetBounds + second_target_bounds = $secondTargetBounds + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-script-blank-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-script-blank-probe.ps1 new file mode 100644 index 000000000..d37fd11dd --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-script-blank-probe.ps1 @@ -0,0 +1,90 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-script-blank" +$port = 8171 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-script-blank.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-script-blank.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-script-blank.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-script-blank.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$popupWorked = $false +$launcherRetained = $false +$serverSawResult = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/script-popup-blank-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "script blank popup server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/script-popup-blank-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "script blank popup window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Script Blank Start" 6 + Start-Sleep -Milliseconds 900 + Send-SmokeCtrlDigit 2 + $titles.result = Wait-TabTitle $browser.Id "Popup Script Blank Result" 24 + $popupWorked = [bool]$titles.result + if (-not $popupWorked) { throw "script blank popup did not open result tab" } + + Send-SmokeCtrlDigit 1 + $titles.returned = Wait-TabTitle $browser.Id "Popup Script Blank Start" 12 + $launcherRetained = [bool]$titles.returned + if (-not $launcherRetained) { throw "script blank popup did not preserve launcher tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawResult = $serverLog -match 'GET /script-popup-blank-result\.html' + } + if (-not $failure -and -not $serverSawResult) { + $failure = "server did not observe script blank result request" + } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + popup_worked = $popupWorked + launcher_retained = $launcherRetained + server_saw_result = $serverSawResult + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-script-named-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-script-named-probe.ps1 new file mode 100644 index 000000000..bd5d416a3 --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-script-named-probe.ps1 @@ -0,0 +1,104 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-script-named" +$port = 8172 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-script-named.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-script-named.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-script-named.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-script-named.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$firstWorked = $false +$secondWorked = $false +$reusedTargetWorked = $false +$serverSawOne = $false +$serverSawTwo = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/script-popup-named-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "script named popup server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/script-popup-named-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "script named popup window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Script Named Start" 6 + Start-Sleep -Milliseconds 1300 + Send-SmokeCtrlDigit 2 + $titles.second = Wait-TabTitle $browser.Id "Popup Script Named Result Two" 30 + $secondWorked = [bool]$titles.second + if (-not $secondWorked) { throw "script named popup did not open second result" } + + Send-SmokeCtrlDigit 1 + $titles.returned = Wait-TabTitle $browser.Id "Popup Script Named Start" 12 + if (-not $titles.returned) { throw "script named popup did not preserve launcher tab" } + + Send-SmokeCtrlDigit 2 + $titles.reused = Wait-TabTitle $browser.Id "Popup Script Named Result Two" 12 + $reusedTargetWorked = [bool]$titles.reused + if (-not $reusedTargetWorked) { throw "script named popup target tab was not reused" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawOne = $serverLog -match 'GET /script-popup-named-one\.html' + $serverSawTwo = $serverLog -match 'GET /script-popup-named-two\.html' + } + $firstWorked = $serverSawOne + if (-not $failure) { + if (-not $serverSawOne) { + $failure = "server did not observe script named result one request" + } elseif (-not $serverSawTwo) { + $failure = "server did not observe script named result two request" + } + } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + first_worked = $firstWorked + second_worked = $secondWorked + reused_target_worked = $reusedTargetWorked + server_saw_one = $serverSawOne + server_saw_two = $serverSawTwo + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-script-policy-block-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-script-policy-block-probe.ps1 new file mode 100644 index 000000000..e53002ae0 --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-script-policy-block-probe.ps1 @@ -0,0 +1,91 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-script-policy-block" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$settingsPath = Join-Path $appDataRoot "browse-settings-v1.txt" +$port = 8177 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-script-policy-block.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-script-policy-block.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-script-policy-block.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-script-policy-block.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Set-Content -Path $settingsPath -Value "lightpanda-browse-settings-v1`nrestore_previous_session`t1`nallow_script_popups`t0`ndefault_zoom_percent`t100`nhomepage_url`t`n" -NoNewline + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Get-ResultHitCount { + param([string]$Path) + if (-not (Test-Path $Path)) { return 0 } + $log = Get-Content $Path -Raw + return ([regex]::Matches($log, 'GET /script-popup-blank-result\.html')).Count +} + +$server = $null +$browser = $null +$ready = $false +$blockedWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/script-popup-blank-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "script popup block server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/script-popup-blank-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "script popup block window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Script Blank Start" 8 + if (-not $titles.initial) { throw "script popup block start page did not load" } + + Start-Sleep -Seconds 2 + $resultHits = Get-ResultHitCount $serverErr + $titles.stillInitial = Wait-TabTitle $browser.Id "Popup Script Blank Start" 4 + $blockedWorked = ($resultHits -eq 0) -and ($titles.stillInitial -ne $null) + if (-not $blockedWorked) { throw "blocked script popup still reached the popup result page" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $finalHits = Get-ResultHitCount $serverErr + if (-not $failure -and $finalHits -ne 0) { + $failure = "expected zero blocked popup result requests but saw $finalHits" + } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + blocked_worked = $blockedWorked + settings_path = $settingsPath + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-popup-script-policy-probe.ps1 b/tmp-browser-smoke/popup/chrome-popup-script-policy-probe.ps1 new file mode 100644 index 000000000..8fdfdc1fa --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-popup-script-policy-probe.ps1 @@ -0,0 +1,116 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-script-policy" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$settingsPath = Join-Path $appDataRoot "browse-settings-v1.txt" +$port = 8176 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-popup-script-policy.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-popup-script-policy.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-popup-script-policy.server.stdout.txt" +$serverErr = Join-Path $root "chrome-popup-script-policy.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Wait-SettingsValue { + param( + [string]$Path, + [string]$Needle, + [int]$TimeoutSeconds = 8 + ) + + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + while ((Get-Date) -lt $deadline) { + if (Test-Path $Path) { + $raw = Get-Content $Path -Raw + if ($raw -match [regex]::Escape($Needle)) { + return $true + } + } + Start-Sleep -Milliseconds 200 + } + return $false +} + +$server = $null +$browser = $null +$ready = $false +$toggleOffWorked = $false +$toggleOnWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/script-popup-policy-index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "script popup policy server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/script-popup-policy-index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "script popup policy window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Popup Policy Start" 8 + if (-not $titles.initial) { throw "script popup policy start page did not load" } + + Send-SmokeCtrlComma + Start-Sleep -Milliseconds 250 + Send-SmokeDown + Start-Sleep -Milliseconds 150 + Send-SmokeSpace + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlComma + $toggleOffWorked = Wait-SettingsValue $settingsPath "allow_script_popups`t0" + if (-not $toggleOffWorked) { throw "settings overlay did not persist script popup policy off" } + + Send-SmokeCtrlComma + Start-Sleep -Milliseconds 250 + Send-SmokeDown + Start-Sleep -Milliseconds 150 + Send-SmokeSpace + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlComma + $toggleOnWorked = Wait-SettingsValue $settingsPath "allow_script_popups`t1" + if (-not $toggleOnWorked) { throw "settings overlay did not persist script popup policy on" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + toggle_off_worked = $toggleOffWorked + toggle_on_worked = $toggleOnWorked + settings_path = $settingsPath + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/chrome-query-load-probe.ps1 b/tmp-browser-smoke/popup/chrome-query-load-probe.ps1 new file mode 100644 index 000000000..44f5ada84 --- /dev/null +++ b/tmp-browser-smoke/popup/chrome-query-load-probe.ps1 @@ -0,0 +1,71 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\popup" +$profileRoot = Join-Path $root "profile-query-load" +$port = 8161 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-query-load.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-query-load.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-query-load.server.stdout.txt" +$serverErr = Join-Path $root "chrome-query-load.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$loadWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/form-result.html?q=popup" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "query load probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/form-result.html?q=popup","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "query load probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.result = Wait-TabTitle $browser.Id "Popup Form Result" + $loadWorked = [bool]$titles.result + if (-not $loadWorked) { throw "query load probe did not reach result title" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + load_worked = $loadWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/popup/form-index.html b/tmp-browser-smoke/popup/form-index.html new file mode 100644 index 000000000..30808a445 --- /dev/null +++ b/tmp-browser-smoke/popup/form-index.html @@ -0,0 +1,14 @@ + + + + + Popup Form Start + + +
+
+ +
+
+ + diff --git a/tmp-browser-smoke/popup/form-result.html b/tmp-browser-smoke/popup/form-result.html new file mode 100644 index 000000000..49bda0c37 --- /dev/null +++ b/tmp-browser-smoke/popup/form-result.html @@ -0,0 +1,12 @@ + + + + + Popup Form Result + + +
+

Popup Form Result

+
+ + diff --git a/tmp-browser-smoke/popup/named-target-index.html b/tmp-browser-smoke/popup/named-target-index.html new file mode 100644 index 000000000..108668f19 --- /dev/null +++ b/tmp-browser-smoke/popup/named-target-index.html @@ -0,0 +1,24 @@ + + + + + Popup Named Anchor Start + + +
+

Two rendered anchors should reuse the same named popup tab.

+ OPEN REPORT ONE + OPEN REPORT TWO +
+ + diff --git a/tmp-browser-smoke/popup/named-target-one.html b/tmp-browser-smoke/popup/named-target-one.html new file mode 100644 index 000000000..ba6f59b11 --- /dev/null +++ b/tmp-browser-smoke/popup/named-target-one.html @@ -0,0 +1,10 @@ + + + + + Popup Named Anchor Result One + + +

Popup Named Anchor Result One

+ + diff --git a/tmp-browser-smoke/popup/named-target-two.html b/tmp-browser-smoke/popup/named-target-two.html new file mode 100644 index 000000000..8dc678dea --- /dev/null +++ b/tmp-browser-smoke/popup/named-target-two.html @@ -0,0 +1,10 @@ + + + + + Popup Named Anchor Result Two + + +

Popup Named Anchor Result Two

+ + diff --git a/tmp-browser-smoke/popup/popup_form_server.py b/tmp-browser-smoke/popup/popup_form_server.py new file mode 100644 index 000000000..77ac335e1 --- /dev/null +++ b/tmp-browser-smoke/popup/popup_form_server.py @@ -0,0 +1,135 @@ +import http.server +import sys +from urllib.parse import parse_qs, urlparse + + +class PopupFormHandler(http.server.BaseHTTPRequestHandler): + def _write_html(self, body: bytes, status: int = 200) -> None: + self.send_response(status) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self) -> None: + parsed = urlparse(self.path) + path = parsed.path + query = parse_qs(parsed.query) + + if path == "/ping": + self._write_html(b"ok") + return + + if path == "/form-post-index.html": + body = ( + b"" + b"Popup Form Post Start" + b"" + b"
" + b"
" + b"" + b"" + b"
" + ) + self._write_html(body) + return + + if path == "/form-target-get-index.html": + body = ( + b"" + b"Popup Named Form Start" + b"" + b"
" + b"

Both submits target the same named popup tab.

" + b"
" + b"" + b"" + b"
" + b"
" + b"" + b"" + b"
" + b"
" + ) + self._write_html(body) + return + + if path == "/form-target-post-index.html": + body = ( + b"" + b"Popup Named Form Post Start" + b"" + b"
" + b"

Both submits target the same named popup tab.

" + b"
" + b"" + b"" + b"
" + b"
" + b"" + b"" + b"
" + b"
" + ) + self._write_html(body) + return + + if path == "/form-target-result.html": + q = query.get("q", [""])[0] + body = ( + "" + f"Popup Named Form Result {q}" + "

Popup Named Form Result

" + ).encode("utf-8") + self._write_html(body) + return + + self.send_error(404) + + def do_POST(self) -> None: + if self.path not in ("/form-post-result.html", "/form-target-post-result.html"): + self.send_error(404) + return + + length = int(self.headers.get("Content-Length", "0")) + raw = self.rfile.read(length) + decoded = raw.decode("utf-8", errors="replace") + parsed = parse_qs(decoded) + q = parsed.get("q", [""])[0] + if self.path == "/form-target-post-result.html": + sys.stderr.write(f"POPUP_TARGET_POST_BODY {decoded}\n") + else: + sys.stderr.write(f"POPUP_POST_BODY {decoded}\n") + sys.stderr.flush() + + if self.path == "/form-target-post-result.html": + body = ( + "" + f"Popup Named Form Post Result {q}" + "

Popup Named Form Post Result

" + ).encode("utf-8") + else: + body = ( + "" + f"Popup Form Post Result {q}" + "

Popup Form Post Result

" + ).encode("utf-8") + self._write_html(body) + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + sys.stderr.flush() + + +def main() -> None: + port = int(sys.argv[1]) + server = http.server.ThreadingHTTPServer(("127.0.0.1", port), PopupFormHandler) + try: + server.serve_forever() + finally: + server.server_close() + + +if __name__ == "__main__": + main() diff --git a/tmp-browser-smoke/popup/script-popup-background-timer.html b/tmp-browser-smoke/popup/script-popup-background-timer.html new file mode 100644 index 000000000..bbc07d34f --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-background-timer.html @@ -0,0 +1,11 @@ + + +Popup Background Timer Start + diff --git a/tmp-browser-smoke/popup/script-popup-blank-index.html b/tmp-browser-smoke/popup/script-popup-blank-index.html new file mode 100644 index 000000000..0c67dbfe3 --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-blank-index.html @@ -0,0 +1,8 @@ + + +Popup Script Blank Start + diff --git a/tmp-browser-smoke/popup/script-popup-blank-result.html b/tmp-browser-smoke/popup/script-popup-blank-result.html new file mode 100644 index 000000000..56926b309 --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-blank-result.html @@ -0,0 +1,4 @@ + + +Popup Script Blank Result +

script blank popup result

diff --git a/tmp-browser-smoke/popup/script-popup-named-index.html b/tmp-browser-smoke/popup/script-popup-named-index.html new file mode 100644 index 000000000..2bdc78ead --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-named-index.html @@ -0,0 +1,11 @@ + + +Popup Script Named Start + diff --git a/tmp-browser-smoke/popup/script-popup-named-one.html b/tmp-browser-smoke/popup/script-popup-named-one.html new file mode 100644 index 000000000..0f5fa21fd --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-named-one.html @@ -0,0 +1,4 @@ + + +Popup Script Named Result One +

script named popup one

diff --git a/tmp-browser-smoke/popup/script-popup-named-two.html b/tmp-browser-smoke/popup/script-popup-named-two.html new file mode 100644 index 000000000..02d48c4d2 --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-named-two.html @@ -0,0 +1,4 @@ + + +Popup Script Named Result Two +

script named popup two

diff --git a/tmp-browser-smoke/popup/script-popup-policy-index.html b/tmp-browser-smoke/popup/script-popup-policy-index.html new file mode 100644 index 000000000..14a160ceb --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-policy-index.html @@ -0,0 +1,18 @@ + + +Popup Policy Start + +
+

Popup Policy Trigger

+

Press P to attempt a script popup.

+
+ + diff --git a/tmp-browser-smoke/popup/script-popup-policy-result.html b/tmp-browser-smoke/popup/script-popup-policy-result.html new file mode 100644 index 000000000..bbb43506b --- /dev/null +++ b/tmp-browser-smoke/popup/script-popup-policy-result.html @@ -0,0 +1,9 @@ + + +Popup Policy Result + +
+

Popup Policy Result

+

This page should open only when script popups are enabled.

+
+ diff --git a/tmp-browser-smoke/rendered-link-dom/chrome-rendered-link-dom-probe.ps1 b/tmp-browser-smoke/rendered-link-dom/chrome-rendered-link-dom-probe.ps1 new file mode 100644 index 000000000..bac7fd107 --- /dev/null +++ b/tmp-browser-smoke/rendered-link-dom/chrome-rendered-link-dom-probe.ps1 @@ -0,0 +1,116 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\rendered-link-dom" +$profileRoot = Join-Path $root "profile" +$port = 8165 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-rendered-link-dom.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-rendered-link-dom.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-rendered-link-dom.server.stdout.txt" +$serverErr = Join-Path $root "chrome-rendered-link-dom.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Invoke-ClientClicksUntilTitle([IntPtr]$Hwnd, [int]$ProcessId, [object[]]$Points, [string]$Needle) { + foreach ($point in $Points) { + [void](Invoke-SmokeClientClick $Hwnd $point.X $point.Y) + $title = Wait-TabTitle $ProcessId $Needle 6 + if ($title) { + return $title + } + Start-Sleep -Milliseconds 150 + } + return $null +} + +$server = $null +$browser = $null +$ready = $false +$preventWorked = $false +$mutateWorked = $false +$serverSawMutated = $false +$serverSawOriginal = $false +$serverSawPrevent = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "rendered link DOM probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","720" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "rendered link DOM probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Rendered Link DOM Start" + if (-not $titles.initial) { throw "rendered link DOM initial title missing" } + + $titles.after_prevent = Invoke-ClientClicksUntilTitle $hwnd $browser.Id @( + @{ X = 76; Y = 165 } + @{ X = 76; Y = 188 } + @{ X = 76; Y = 213 } + ) "Rendered Prevented Click" + $preventWorked = [bool]$titles.after_prevent + if (-not $preventWorked) { throw "preventDefault rendered link click did not keep page and update title" } + + Start-Sleep -Milliseconds 500 + $titles.after_mutate = Invoke-ClientClicksUntilTitle $hwnd $browser.Id @( + @{ X = 212; Y = 165 } + @{ X = 212; Y = 188 } + @{ X = 212; Y = 213 } + ) "Rendered Mutated Result" + $mutateWorked = [bool]$titles.after_mutate + if (-not $mutateWorked) { throw "onclick href mutation did not navigate to mutated result" } + + Start-Sleep -Milliseconds 500 + $serverLog = if (Test-Path $serverErr) { Get-Content $serverErr -Raw } else { "" } + $serverSawMutated = $serverLog -match 'GET /mutated-target\.html\?from=onclick' + $serverSawOriginal = $serverLog -match 'GET /original-target\.html' + $serverSawPrevent = $serverLog -match 'GET /prevent-default-should-not-load\.html' + if (-not $serverSawMutated) { throw "server did not observe mutated-target request" } + if ($serverSawOriginal) { throw "server observed original-target request; onclick href mutation was ignored" } + if ($serverSawPrevent) { throw "server observed prevent-default target request" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + prevent_worked = $preventWorked + mutate_worked = $mutateWorked + server_saw_mutated = $serverSawMutated + server_saw_original = $serverSawOriginal + server_saw_prevent = $serverSawPrevent + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/rendered-link-dom/index.html b/tmp-browser-smoke/rendered-link-dom/index.html new file mode 100644 index 000000000..da7f8921d --- /dev/null +++ b/tmp-browser-smoke/rendered-link-dom/index.html @@ -0,0 +1,32 @@ + + + + + Rendered Link DOM Start + + +
+

Rendered link activation should honor onclick and preventDefault before any direct fallback.

+ PREVENT DEFAULT + MUTATE HREF +
+ + + diff --git a/tmp-browser-smoke/rendered-link-dom/mutated-target.html b/tmp-browser-smoke/rendered-link-dom/mutated-target.html new file mode 100644 index 000000000..5a5a03faf --- /dev/null +++ b/tmp-browser-smoke/rendered-link-dom/mutated-target.html @@ -0,0 +1,12 @@ + + + + + Rendered Mutated Result + + +
+

Rendered Mutated Result

+
+ + diff --git a/tmp-browser-smoke/rendered-link-dom/original-target.html b/tmp-browser-smoke/rendered-link-dom/original-target.html new file mode 100644 index 000000000..9650fd6b9 --- /dev/null +++ b/tmp-browser-smoke/rendered-link-dom/original-target.html @@ -0,0 +1,12 @@ + + + + + Rendered Original Target + + +
+

Rendered Original Target

+
+ + diff --git a/tmp-browser-smoke/rendered-link-dom/prevent-default-should-not-load.html b/tmp-browser-smoke/rendered-link-dom/prevent-default-should-not-load.html new file mode 100644 index 000000000..079ca5014 --- /dev/null +++ b/tmp-browser-smoke/rendered-link-dom/prevent-default-should-not-load.html @@ -0,0 +1,12 @@ + + + + + Rendered Prevent Default Should Not Load + + +
+

Rendered Prevent Default Should Not Load

+
+ + diff --git a/tmp-browser-smoke/sessionstorage-scope/SessionStorageProbeCommon.ps1 b/tmp-browser-smoke/sessionstorage-scope/SessionStorageProbeCommon.ps1 new file mode 100644 index 000000000..32005fba3 --- /dev/null +++ b/tmp-browser-smoke/sessionstorage-scope/SessionStorageProbeCommon.ps1 @@ -0,0 +1,104 @@ +$script:Repo = "C:\Users\adyba\src\lightpanda-browser" +$script:Root = Join-Path $script:Repo "tmp-browser-smoke\sessionstorage-scope" +$script:BrowserExe = Join-Path $script:Repo "zig-out\bin\lightpanda.exe" + +. "$script:Repo\tmp-browser-smoke\common\Win32Input.ps1" +. "$script:Repo\tmp-browser-smoke\tabs\TabProbeCommon.ps1" + +function Reset-SessionStorageProfile([string]$ProfileRoot) { + $appDataRoot = Join-Path $ProfileRoot "lightpanda" + $downloadsDir = Join-Path $appDataRoot "downloads" + cmd /c "rmdir /s /q `"$ProfileRoot`"" | Out-Null + New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null + $env:APPDATA = $ProfileRoot + $env:LOCALAPPDATA = $ProfileRoot + return @{ + AppDataRoot = $appDataRoot + DownloadsDir = $downloadsDir + } +} + +function Seed-SessionStorageProfile([string]$AppDataRoot) { +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $AppDataRoot "browse-settings-v1.txt") -NoNewline +} + +function Wait-SessionStorageServer([int]$Port, [int]$Attempts = 30) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$Port/seed.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { return $true } + } catch {} + } + return $false +} + +function Start-SessionStorageServer([int]$Port, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath "python" -ArgumentList (Join-Path $script:Root "session_storage_server.py"),"$Port" -WorkingDirectory $script:Root -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Start-SessionStorageBrowser([string]$StartupUrl, [string]$Stdout, [string]$Stderr) { + return Start-Process -FilePath $script:BrowserExe -ArgumentList "browse",$StartupUrl,"--window_width","960","--window_height","640" -WorkingDirectory $script:Repo -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr +} + +function Invoke-SessionStorageAddressCommit([IntPtr]$Hwnd, [string]$Url) { + [void](Invoke-SmokeClientClick $Hwnd 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 120 + Send-SmokeText $Url + Start-Sleep -Milliseconds 120 + Send-SmokeEnter +} + +function Invoke-SessionStorageAddressNavigate([IntPtr]$Hwnd, [int]$BrowserId, [string]$Url, [string]$Needle) { + Invoke-SessionStorageAddressCommit $Hwnd $Url + return Wait-SessionStorageWindowTitle $Hwnd $Needle 40 +} + +function Wait-SessionStorageWindowTitle([IntPtr]$Hwnd, [string]$Needle, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + $title = Get-SmokeWindowTitle $Hwnd + if ($title -like "*$Needle*") { + return $title + } + } + return $null +} + +function Format-SessionStorageProbeProcessMeta($Meta) { + if (-not $Meta) { return $null } + return [ordered]@{ + name = [string]$Meta.Name + pid = [int]$Meta.ProcessId + command_line = [string]$Meta.CommandLine + created = [string]$Meta.CreationDate + } +} + +function Write-SessionStorageProbeResult($Result, [string]$Prefix = "") { + foreach ($entry in $Result.GetEnumerator()) { + $key = if ($Prefix) { "$Prefix$($entry.Key)" } else { [string]$entry.Key } + $value = $entry.Value + if ($value -is [System.Collections.IDictionary]) { + Write-SessionStorageProbeResult $value "$key." + continue + } + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $joined = ($value | ForEach-Object { [string]$_ }) -join "," + Write-Output ("{0}={1}" -f $key, $joined) + continue + } + $text = if ($null -eq $value) { "" } else { [string]$value } + $text = $text -replace "`r", "\\r" + $text = $text -replace "`n", "\\n" + Write-Output ("{0}={1}" -f $key, $text) + } +} diff --git a/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-cross-tab-probe.ps1 b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-cross-tab-probe.ps1 new file mode 100644 index 000000000..f97874d6c --- /dev/null +++ b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-cross-tab-probe.ps1 @@ -0,0 +1,72 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\sessionstorage-scope\SessionStorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-sessionstorage-cross-tab" +$app = Reset-SessionStorageProfile $profileRoot +Seed-SessionStorageProfile $app.AppDataRoot +$port = 8421 +$origin = "http://127.0.0.1:$port" +$browserOut = Join-Path $Root "chrome-sessionstorage-cross-tab.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-sessionstorage-cross-tab.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-sessionstorage-cross-tab.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-sessionstorage-cross-tab.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$echoMissing = $false +$seedTabRetained = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-SessionStorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-SessionStorageServer -Port $port + if (-not $ready) { throw "sessionStorage server did not become ready" } + + $browser = Start-SessionStorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "sessionStorage cross-tab window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-SessionStorageWindowTitle $hwnd "Session Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish sessionStorage write" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 350 + $titles.echo = Invoke-SessionStorageAddressNavigate $hwnd $browser.Id "$origin/echo.html" "Session Storage Echo missing" + $echoMissing = [bool]$titles.echo + if (-not $echoMissing) { throw "new tab unexpectedly saw sessionStorage from the first tab" } + + Send-SmokeCtrlShiftTab + $titles.back_to_seed = Wait-SessionStorageWindowTitle $hwnd "Session Storage Seeded" 30 + $seedTabRetained = [bool]$titles.back_to_seed + if (-not $seedTabRetained) { throw "seed tab was not preserved after cross-tab check" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + echo_missing = $echoMissing + seed_tab_retained = $seedTabRetained + titles = $titles + error = $failure + server_meta = Format-SessionStorageProbeProcessMeta $serverMeta + browser_meta = Format-SessionStorageProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-SessionStorageProbeResult $result + if ($failure -or -not $seedWorked -or -not $echoMissing -or -not $seedTabRetained) { exit 1 } +} diff --git a/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-restart-probe.ps1 b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-restart-probe.ps1 new file mode 100644 index 000000000..5b28ed3c5 --- /dev/null +++ b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-restart-probe.ps1 @@ -0,0 +1,80 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\sessionstorage-scope\SessionStorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-sessionstorage-restart" +$app = Reset-SessionStorageProfile $profileRoot +Seed-SessionStorageProfile $app.AppDataRoot +$port = 8422 +$origin = "http://127.0.0.1:$port" +$browserOneOut = Join-Path $Root "chrome-sessionstorage-restart.run1.browser.stdout.txt" +$browserOneErr = Join-Path $Root "chrome-sessionstorage-restart.run1.browser.stderr.txt" +$browserTwoOut = Join-Path $Root "chrome-sessionstorage-restart.run2.browser.stdout.txt" +$browserTwoErr = Join-Path $Root "chrome-sessionstorage-restart.run2.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-sessionstorage-restart.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-sessionstorage-restart.server.stderr.txt" +Remove-Item $browserOneOut,$browserOneErr,$browserTwoOut,$browserTwoErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browserOne = $null +$browserTwo = $null +$ready = $false +$seedWorked = $false +$restartMissing = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-SessionStorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-SessionStorageServer -Port $port + if (-not $ready) { throw "sessionStorage server did not become ready" } + + $browserOne = Start-SessionStorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOneOut -Stderr $browserOneErr + $hwndOne = Wait-TabWindowHandle $browserOne.Id + if ($hwndOne -eq [IntPtr]::Zero) { throw "sessionStorage restart run1 window handle not found" } + Show-SmokeWindow $hwndOne + + $titles.seed = Wait-SessionStorageWindowTitle $hwndOne "Session Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish sessionStorage write" } + + $null = Stop-OwnedProbeProcess $browserOne + $browserOne = $null + Start-Sleep -Milliseconds 500 + + $browserTwo = Start-SessionStorageBrowser -StartupUrl "$origin/echo.html" -Stdout $browserTwoOut -Stderr $browserTwoErr + $hwndTwo = Wait-TabWindowHandle $browserTwo.Id + if ($hwndTwo -eq [IntPtr]::Zero) { throw "sessionStorage restart run2 window handle not found" } + Show-SmokeWindow $hwndTwo + + $titles.restart = Wait-SessionStorageWindowTitle $hwndTwo "Session Storage Echo missing" 40 + $restartMissing = [bool]$titles.restart + if (-not $restartMissing) { throw "sessionStorage unexpectedly persisted across browser restart" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserOneMeta = if ($browserOne) { Stop-OwnedProbeProcess $browserOne } else { $null } + $browserTwoMeta = if ($browserTwo) { Stop-OwnedProbeProcess $browserTwo } else { $null } + Start-Sleep -Milliseconds 200 + $browserOneGone = if ($browserOne) { -not (Get-Process -Id $browserOne.Id -ErrorAction SilentlyContinue) } else { $true } + $browserTwoGone = if ($browserTwo) { -not (Get-Process -Id $browserTwo.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_one_pid = if ($browserOne) { $browserOne.Id } else { 0 } + browser_two_pid = if ($browserTwo) { $browserTwo.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + restart_missing = $restartMissing + titles = $titles + error = $failure + server_meta = Format-SessionStorageProbeProcessMeta $serverMeta + browser_one_meta = Format-SessionStorageProbeProcessMeta $browserOneMeta + browser_two_meta = Format-SessionStorageProbeProcessMeta $browserTwoMeta + browser_one_gone = $browserOneGone + browser_two_gone = $browserTwoGone + server_gone = $serverGone + } + Write-SessionStorageProbeResult $result + if ($failure -or -not $seedWorked -or -not $restartMissing) { exit 1 } +} diff --git a/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-same-tab-probe.ps1 b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-same-tab-probe.ps1 new file mode 100644 index 000000000..a70c77f9f --- /dev/null +++ b/tmp-browser-smoke/sessionstorage-scope/chrome-sessionstorage-same-tab-probe.ps1 @@ -0,0 +1,63 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +. "$repo\tmp-browser-smoke\sessionstorage-scope\SessionStorageProbeCommon.ps1" + +$profileRoot = Join-Path $Root "profile-sessionstorage-same-tab" +$app = Reset-SessionStorageProfile $profileRoot +Seed-SessionStorageProfile $app.AppDataRoot +$port = 8420 +$origin = "http://127.0.0.1:$port" +$browserOut = Join-Path $Root "chrome-sessionstorage-same-tab.browser.stdout.txt" +$browserErr = Join-Path $Root "chrome-sessionstorage-same-tab.browser.stderr.txt" +$serverOut = Join-Path $Root "chrome-sessionstorage-same-tab.server.stdout.txt" +$serverErr = Join-Path $Root "chrome-sessionstorage-same-tab.server.stderr.txt" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$server = $null +$browser = $null +$ready = $false +$seedWorked = $false +$echoWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-SessionStorageServer -Port $port -Stdout $serverOut -Stderr $serverErr + $ready = Wait-SessionStorageServer -Port $port + if (-not $ready) { throw "sessionStorage server did not become ready" } + + $browser = Start-SessionStorageBrowser -StartupUrl "$origin/seed.html" -Stdout $browserOut -Stderr $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "sessionStorage same-tab window handle not found" } + Show-SmokeWindow $hwnd + + $titles.seed = Wait-SessionStorageWindowTitle $hwnd "Session Storage Seeded" 40 + $seedWorked = [bool]$titles.seed + if (-not $seedWorked) { throw "seed page did not finish sessionStorage write" } + + $titles.echo = Invoke-SessionStorageAddressNavigate $hwnd $browser.Id "$origin/echo.html" "Session Storage Echo ok" + $echoWorked = [bool]$titles.echo + if (-not $echoWorked) { throw "same tab did not preserve sessionStorage across navigation" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + $result = [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + seed_worked = $seedWorked + echo_worked = $echoWorked + titles = $titles + error = $failure + server_meta = Format-SessionStorageProbeProcessMeta $serverMeta + browser_meta = Format-SessionStorageProbeProcessMeta $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } + Write-SessionStorageProbeResult $result + if ($failure -or -not $seedWorked -or -not $echoWorked) { exit 1 } +} diff --git a/tmp-browser-smoke/sessionstorage-scope/session_storage_server.py b/tmp-browser-smoke/sessionstorage-scope/session_storage_server.py new file mode 100644 index 000000000..1cd6732fb --- /dev/null +++ b/tmp-browser-smoke/sessionstorage-scope/session_storage_server.py @@ -0,0 +1,73 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys + + +def html(title: str, body: str) -> bytes: + return ( + "" + f"{title}{body}" + ).encode("utf-8") + + +class Handler(BaseHTTPRequestHandler): + server_version = "SessionStorageSmoke/1.0" + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + + def do_GET(self): + if self.path == "/favicon.ico": + self.send_response(204) + self.end_headers() + return + + if self.path == "/seed.html": + body = html( + "Session Storage Loading - Lightpanda Browser", + "

seed

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/echo.html": + body = html( + "Session Storage Loading - Lightpanda Browser", + "

echo

", + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = html("Not Found - Lightpanda Browser", "

Not Found

") + self.send_response(404) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + +def main() -> int: + port = 8420 + if len(sys.argv) > 1: + port = int(sys.argv[1]) + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/settings/chrome-settings-home-probe.ps1 b/tmp-browser-smoke/settings/chrome-settings-home-probe.ps1 new file mode 100644 index 000000000..c0e881355 --- /dev/null +++ b/tmp-browser-smoke/settings/chrome-settings-home-probe.ps1 @@ -0,0 +1,116 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\settings" +$profileRoot = Join-Path $root "profile-home" +$port = 8155 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-settings-home.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-settings-home.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-settings-home.server.stdout.txt" +$serverErr = Join-Path $root "chrome-settings-home.server.stderr.txt" +$settingsFile = Join-Path $profileRoot "lightpanda\browse-settings-v1.txt" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Remove-Item $profileRoot -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Path $profileRoot -Force | Out-Null + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\common\Win32Input.ps1" +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Wait-SettingsFileMatch([string]$Path, [string]$Needle, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $Path) -and ((Get-Content $Path -Raw) -like "*$Needle*")) { + return $true + } + } + return $false +} + +$server = $null +$browser = $null +$ready = $false +$defaultZoomSaved = $false +$homepageSaved = $false +$homeWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/home.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "settings home probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/home.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "settings home probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Settings Home" + if (-not $titles.initial) { throw "settings home probe initial title missing" } + + Send-SmokeCtrlComma + Start-Sleep -Milliseconds 200 + Send-SmokeDown + Start-Sleep -Milliseconds 100 + Send-SmokeRight + $defaultZoomSaved = Wait-SettingsFileMatch $settingsFile "default_zoom_percent`t110" + if (-not $defaultZoomSaved) { throw "settings home probe did not persist default zoom" } + + Send-SmokeDown + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $homepageSaved = Wait-SettingsFileMatch $settingsFile "homepage_url`thttp://127.0.0.1:$port/home.html" + if (-not $homepageSaved) { throw "settings home probe did not persist homepage" } + + Send-SmokeCtrlComma + Start-Sleep -Milliseconds 150 + [void](Invoke-SmokeClientClick $hwnd 160 40) + Start-Sleep -Milliseconds 120 + Send-SmokeText "http://127.0.0.1:$port/index.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $titles.index = Wait-TabTitle $browser.Id "Settings Start" + if (-not $titles.index) { throw "settings home probe did not navigate to index page" } + + Send-SmokeAltHome + $titles.after_home = Wait-TabTitle $browser.Id "Settings Home" + $homeWorked = [bool]$titles.after_home + if (-not $homeWorked) { throw "settings home probe alt+home did not navigate to homepage" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + $browserMeta = if ($browser) { Stop-OwnedProbeProcess $browser } else { $null } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + default_zoom_saved = $defaultZoomSaved + homepage_saved = $homepageSaved + home_worked = $homeWorked + settings_file = $settingsFile + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/settings/chrome-settings-restore-off-probe.ps1 b/tmp-browser-smoke/settings/chrome-settings-restore-off-probe.ps1 new file mode 100644 index 000000000..e3f95984f --- /dev/null +++ b/tmp-browser-smoke/settings/chrome-settings-restore-off-probe.ps1 @@ -0,0 +1,131 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\settings" +$profileRoot = Join-Path $root "profile-restore-off" +$port = 8156 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$run1Out = Join-Path $root "chrome-settings-restore-off.run1.browser.stdout.txt" +$run1Err = Join-Path $root "chrome-settings-restore-off.run1.browser.stderr.txt" +$run2Out = Join-Path $root "chrome-settings-restore-off.run2.browser.stdout.txt" +$run2Err = Join-Path $root "chrome-settings-restore-off.run2.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-settings-restore-off.server.stdout.txt" +$serverErr = Join-Path $root "chrome-settings-restore-off.server.stderr.txt" +$settingsFile = Join-Path $profileRoot "lightpanda\browse-settings-v1.txt" +$sessionFile = Join-Path $profileRoot "lightpanda\browse-session-v1.txt" + +Remove-Item $run1Out,$run1Err,$run2Out,$run2Err,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Remove-Item $profileRoot -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Path $profileRoot -Force | Out-Null + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\..\common\Win32Input.ps1" +. "$PSScriptRoot\..\tabs\TabProbeCommon.ps1" + +function Wait-SettingsFileMatch([string]$Path, [string]$Needle, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $Path) -and ((Get-Content $Path -Raw) -like "*$Needle*")) { + return $true + } + } + return $false +} + +$server = $null +$browser1 = $null +$browser2 = $null +$ready = $false +$sessionPrepared = $false +$restoreDisabledSaved = $false +$sessionCleared = $false +$notRestored = $false +$titles = [ordered]@{} +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "settings restore-off probe server did not become ready" } + + $browser1 = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $run1Out -RedirectStandardError $run1Err + $hwnd1 = Wait-TabWindowHandle $browser1.Id + if ($hwnd1 -eq [IntPtr]::Zero) { throw "settings restore-off run1 window handle not found" } + Show-SmokeWindow $hwnd1 + $titles.run1_initial = Wait-TabTitle $browser1.Id "Settings Start" + if (-not $titles.run1_initial) { throw "settings restore-off run1 initial title missing" } + + Send-SmokeCtrlT + Start-Sleep -Milliseconds 200 + $titles.run1_new_tab = Wait-TabTitle $browser1.Id "New Tab" + if (-not $titles.run1_new_tab) { throw "settings restore-off run1 new tab missing" } + + [void](Invoke-SmokeClientClick $hwnd1 160 40) + Start-Sleep -Milliseconds 120 + Send-SmokeText "http://127.0.0.1:$port/home.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $titles.run1_second = Wait-TabTitle $browser1.Id "Settings Home" + if (-not $titles.run1_second) { throw "settings restore-off run1 second tab title missing" } + $sessionPrepared = $true + + Send-SmokeCtrlComma + Start-Sleep -Milliseconds 200 + Send-SmokeSpace + $restoreDisabledSaved = Wait-SettingsFileMatch $settingsFile "restore_previous_session`t0" + if (-not $restoreDisabledSaved) { throw "settings restore-off did not persist restore_previous_session=0" } + + $browser1Meta = Stop-OwnedProbeProcess $browser1 + Start-Sleep -Milliseconds 500 + $sessionCleared = -not (Test-Path $sessionFile) + if (-not $sessionCleared) { throw "settings restore-off did not clear saved session file" } + + $browser2 = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $run2Out -RedirectStandardError $run2Err + $hwnd2 = Wait-TabWindowHandle $browser2.Id + if ($hwnd2 -eq [IntPtr]::Zero) { throw "settings restore-off run2 window handle not found" } + Show-SmokeWindow $hwnd2 + $titles.run2_initial = Wait-TabTitle $browser2.Id "Settings Start" + $notRestored = [bool]$titles.run2_initial + if (-not $notRestored) { throw "settings restore-off reopened a restored tab instead of startup page" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Stop-OwnedProbeProcess $server } else { $null } + if (-not $browser1Meta) { $browser1Meta = if ($browser1) { Stop-OwnedProbeProcess $browser1 } else { $null } } + $browser2Meta = if ($browser2) { Stop-OwnedProbeProcess $browser2 } else { $null } + Start-Sleep -Milliseconds 200 + $browser1Gone = if ($browser1) { -not (Get-Process -Id $browser1.Id -ErrorAction SilentlyContinue) } else { $true } + $browser2Gone = if ($browser2) { -not (Get-Process -Id $browser2.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + run1_browser_pid = if ($browser1) { $browser1.Id } else { 0 } + run2_browser_pid = if ($browser2) { $browser2.Id } else { 0 } + ready = $ready + session_prepared = $sessionPrepared + restore_disabled_saved = $restoreDisabledSaved + session_cleared = $sessionCleared + not_restored = $notRestored + settings_file = $settingsFile + session_file = $sessionFile + titles = $titles + error = $failure + server_meta = $serverMeta + browser1_meta = $browser1Meta + browser2_meta = $browser2Meta + browser1_gone = $browser1Gone + browser2_gone = $browser2Gone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/settings/home.html b/tmp-browser-smoke/settings/home.html new file mode 100644 index 000000000..dbf836b5f --- /dev/null +++ b/tmp-browser-smoke/settings/home.html @@ -0,0 +1,13 @@ + + + + + Settings Home + + +
+

Settings Home

+

This page becomes the persisted home target in the headed settings probe.

+
+ + diff --git a/tmp-browser-smoke/settings/index.html b/tmp-browser-smoke/settings/index.html new file mode 100644 index 000000000..e9c127176 --- /dev/null +++ b/tmp-browser-smoke/settings/index.html @@ -0,0 +1,13 @@ + + + + + Settings Start + + +
+

Settings Start

+

This page is the non-home fallback for settings probes.

+
+ + diff --git a/tmp-browser-smoke/stop-loading/chrome-stop-input-probe.ps1 b/tmp-browser-smoke/stop-loading/chrome-stop-input-probe.ps1 new file mode 100644 index 000000000..f28f4652b --- /dev/null +++ b/tmp-browser-smoke/stop-loading/chrome-stop-input-probe.ps1 @@ -0,0 +1,194 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stop-loading" +$port = 8153 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "slow_server.py" +$browserOut = Join-Path $root "chrome-stop-input.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-stop-input.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-stop-input.server.stdout.txt" +$serverErr = Join-Path $root "chrome-stop-input.server.stderr.txt" +$beforePng = Join-Path $root "chrome-stop-input.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$beforePng -Force -ErrorAction SilentlyContinue + +. (Join-Path (Split-Path $PSScriptRoot -Parent) "common\Win32Input.ps1") + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$browserRunningAfterStop = $false +$preflightWorked = $false +$restoredInputWorked = $false +$usedTabFallback = $false +$usedClickFallback = $false +$titleBefore = $null +$titleAfterPreflight = $null +$titleAfterStop = $null +$titleAfterRestoreInput = $null +$slowStarted = $false +$serverSawAbort = $false +$serverSawResponse = $false +$failure = $null + +function Wait-ForTitleLike([IntPtr]$Hwnd, [string]$Pattern, [int]$Attempts = 20, [int]$SleepMs = 250) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds $SleepMs + $title = Get-SmokeWindowTitle $Hwnd + if ($title -like $Pattern) { + return $title + } + } + return $null +} + +function Try-TypeIntoRestoredInput([IntPtr]$Hwnd, [string]$Text, [string]$Pattern, [switch]$AllowTabFallback) { + Send-SmokeText $Text + $title = Wait-ForTitleLike $Hwnd $Pattern 10 200 + if ($title) { return [ordered]@{ title = $title; click = $false; tab = $false } } + + [void](Invoke-SmokeClientClick $Hwnd 150 230) + Start-Sleep -Milliseconds 120 + Send-SmokeText $Text + $title = Wait-ForTitleLike $Hwnd $Pattern 10 200 + if ($title) { return [ordered]@{ title = $title; click = $true; tab = $false } } + + if ($AllowTabFallback) { + Send-SmokeTab + Start-Sleep -Milliseconds 120 + Send-SmokeText $Text + $title = Wait-ForTitleLike $Hwnd $Pattern 10 200 + if ($title) { return [ordered]@{ title = $title; click = $false; tab = $true } } + } + + return $null +} + +try { + $server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/ping" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "stop input probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/input.html","--window_width","260","--window_height","520","--screenshot_png",$beforePng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "stop input probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "stop input probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + $preflight = Try-TypeIntoRestoredInput $hwnd "A" "Stop Restore Input A*" -AllowTabFallback + if ($preflight) { + $titleAfterPreflight = $preflight.title + $preflightWorked = $true + $usedClickFallback = $preflight.click + $usedTabFallback = $preflight.tab + } else { + throw "preflight page input did not update the title" + } + + [void](Invoke-SmokeClientClick $hwnd 130 40) + Start-Sleep -Milliseconds 200 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 100 + Send-SmokeText "http://127.0.0.1:$port/slow.html" + Start-Sleep -Milliseconds 200 + Send-SmokeEnter + + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + if ($serverLog -match 'SLOW_RESPONSE_BEGIN /slow\.html') { + $slowStarted = $true + break + } + } + } + if (-not $slowStarted) { throw "slow navigation did not begin" } + + [void](Invoke-SmokeClientClick $hwnd 89 40) + Start-Sleep -Milliseconds 350 + + $procAfter = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + $browserRunningAfterStop = $null -ne $procAfter + if (-not $browserRunningAfterStop) { + throw "browser exited after stop" + } + $titleAfterStop = Get-SmokeWindowTitle $hwnd + + $restoreInput = Try-TypeIntoRestoredInput $hwnd "B" "Stop Restore Input AB*" -AllowTabFallback + if ($restoreInput) { + $titleAfterRestoreInput = $restoreInput.title + if ($restoreInput.click) { $usedClickFallback = $true } + if ($restoreInput.tab) { $usedTabFallback = $true } + } + $restoredInputWorked = $null -ne $titleAfterRestoreInput + + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawAbort = $serverLog -match 'SLOW_RESPONSE_ABORTED /slow\.html' + $serverSawResponse = $serverLog -match 'SLOW_RESPONSE_SENT /slow\.html' + } + + if (-not $restoredInputWorked) { + throw "restored page input did not preserve state and extend to AB after stop" + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + title_before = $titleBefore + title_after_preflight = $titleAfterPreflight + title_after_stop = $titleAfterStop + title_after_restore_input = $titleAfterRestoreInput + preflight_worked = $preflightWorked + slow_started = $slowStarted + browser_running_after_stop = $browserRunningAfterStop + restored_input_worked = $restoredInputWorked + used_click_fallback = $usedClickFallback + used_tab_fallback = $usedTabFallback + server_saw_abort = $serverSawAbort + server_saw_response = $serverSawResponse + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/stop-loading/chrome-stop-probe.ps1 b/tmp-browser-smoke/stop-loading/chrome-stop-probe.ps1 new file mode 100644 index 000000000..839756211 --- /dev/null +++ b/tmp-browser-smoke/stop-loading/chrome-stop-probe.ps1 @@ -0,0 +1,153 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stop-loading" +$port = 8152 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "slow_server.py" +$browserOut = Join-Path $root "chrome-stop.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-stop.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-stop.server.stdout.txt" +$serverErr = Join-Path $root "chrome-stop.server.stderr.txt" +$beforePng = Join-Path $root "chrome-stop.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$beforePng -Force -ErrorAction SilentlyContinue + +. (Join-Path (Split-Path $PSScriptRoot -Parent) "common\Win32Input.ps1") + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$browserRunningAfterStop = $false +$liveContextRestored = $false +$titleBefore = $null +$titleAfterStop = $null +$titleAfterResume = $null +$slowStarted = $false +$serverSawAbort = $false +$serverSawResponse = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/ping" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "stop probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$beforePng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "stop probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "stop probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + + [void](Invoke-SmokeClientClick $hwnd 120 40) + Start-Sleep -Milliseconds 200 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 100 + Send-SmokeText "http://127.0.0.1:$port/slow.html" + Start-Sleep -Milliseconds 200 + Send-SmokeEnter + + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + if ($serverLog -match 'SLOW_RESPONSE_BEGIN /slow\.html') { + $slowStarted = $true + break + } + } + } + if (-not $slowStarted) { throw "slow navigation did not begin" } + + [void](Invoke-SmokeClientClick $hwnd 89 40) + Start-Sleep -Milliseconds 250 + + $procAfter = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + $browserRunningAfterStop = $null -ne $procAfter + if (-not $browserRunningAfterStop) { + throw "browser exited after stop" + } + $titleAfterStop = Get-SmokeWindowTitle $hwnd + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $procTick = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + $browserRunningAfterStop = $null -ne $procTick + if (-not $browserRunningAfterStop) { + break + } + $currentTitle = Get-SmokeWindowTitle $hwnd + if ($currentTitle -ne $titleAfterStop -and $currentTitle -like "Stop Restore Tick *") { + $titleAfterResume = $currentTitle + $liveContextRestored = $true + } + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawAbort = $serverLog -match 'SLOW_RESPONSE_ABORTED /slow\.html' + $serverSawResponse = $serverLog -match 'SLOW_RESPONSE_SENT /slow\.html' + if ($liveContextRestored -and ($serverSawAbort -or $serverSawResponse)) { + break + } + } + } + + if (Test-Path $serverErr) { + $serverLog = Get-Content $serverErr -Raw + $serverSawAbort = $serverLog -match 'SLOW_RESPONSE_ABORTED /slow\.html' + $serverSawResponse = $serverLog -match 'SLOW_RESPONSE_SENT /slow\.html' + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + title_before = $titleBefore + title_after_stop = $titleAfterStop + title_after_resume = $titleAfterResume + slow_started = $slowStarted + browser_running_after_stop = $browserRunningAfterStop + live_context_restored = $liveContextRestored + server_saw_abort = $serverSawAbort + server_saw_response = $serverSawResponse + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/stop-loading/slow_server.py b/tmp-browser-smoke/stop-loading/slow_server.py new file mode 100644 index 000000000..a9f41d0cb --- /dev/null +++ b/tmp-browser-smoke/stop-loading/slow_server.py @@ -0,0 +1,116 @@ +import http.server +import socketserver +import sys +import time + + +class SlowHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/ping": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/" or self.path == "/index.html": + body = ( + b"" + b"Stop Restore Base" + b"" + b"" + b"" + b"
" + b"

Base page before the slow navigation.

" + b"

" + b"The page title should keep ticking forward after stop if the live context resumes.

" + b"

" + b"" + b"OPEN SLOW" + b"

" + b"
" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/input.html": + body = ( + b"" + b"Stop Restore Input Base" + b"" + b"" + b"
" + b"

" + b"Type into the input, navigate to the slow page, stop, then continue typing.

" + b"

" + b"" + b"

" + b"

" + b"" + b"OPEN SLOW" + b"

" + b"
" + b"" + b"" + ) + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/slow.html": + sys.stderr.write("SLOW_RESPONSE_BEGIN /slow.html\n") + sys.stderr.flush() + time.sleep(5) + body = ( + b"" + b"Slow Target

Slow Target

" + b"

If this finishes, stop failed.

" + ) + try: + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + sys.stderr.write("SLOW_RESPONSE_SENT /slow.html\n") + sys.stderr.flush() + except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError): + sys.stderr.write("SLOW_RESPONSE_ABORTED /slow.html\n") + sys.stderr.flush() + return + + self.send_error(404) + + def log_message(self, fmt, *args): + sys.stderr.write("%s - - [%s] %s\n" % (self.client_address[0], self.log_date_time_string(), fmt % args)) + sys.stderr.flush() + + +def main(): + port = int(sys.argv[1]) + with socketserver.TCPServer(("127.0.0.1", port), SlowHandler) as server: + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-anonymous-probe.ps1 b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-anonymous-probe.ps1 new file mode 100644 index 000000000..ff1d944c9 --- /dev/null +++ b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-anonymous-probe.ps1 @@ -0,0 +1,96 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stylesheet-smoke" +$profileRoot = Join-Path $root "profile-stylesheet-auth-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8160 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "stylesheet_server.py" +$browserOut = Join-Path $root "stylesheet-auth-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "stylesheet-auth-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "stylesheet-auth-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "stylesheet-auth-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "stylesheet.requests.jsonl" +$pageUrl = "http://css%20user:p%40ss@127.0.0.1:$port/auth-stylesheet-anonymous-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-stylesheet-anonymous-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost stylesheet anonymous auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$loaded = $false +$cssEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $cssEntries = @($entries | Where-Object { $_.path -eq "/private-anonymous.css" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded-anon" }) + if ($cssEntries.Count -gt 0) { $cssEntry = $cssEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($cssEntry -and $loadedEntry) { + $loaded = $true + break + } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + loaded = $loaded + stylesheet_allowed = if ($cssEntry) { [bool]$cssEntry.allowed } else { $false } + stylesheet_user_agent = if ($cssEntry) { [string]$cssEntry.user_agent } else { "" } + stylesheet_cookie = if ($cssEntry) { [string]$cssEntry.cookie } else { "" } + stylesheet_referer = if ($cssEntry) { [string]$cssEntry.referer } else { "" } + stylesheet_authorization = if ($cssEntry) { [string]$cssEntry.authorization } else { "" } + stylesheet_accept = if ($cssEntry) { [string]$cssEntry.accept } else { "" } + loaded_sheet = if ($loadedEntry) { [string]$loadedEntry.sheet } else { "" } + loaded_count = if ($loadedEntry) { [string]$loadedEntry.count } else { "" } + loaded_applied = if ($loadedEntry) { [string]$loadedEntry.applied } else { "" } + loaded_bg = if ($loadedEntry) { [string]$loadedEntry.bg } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-probe.ps1 b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-probe.ps1 new file mode 100644 index 000000000..103f1cd45 --- /dev/null +++ b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-auth-probe.ps1 @@ -0,0 +1,96 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stylesheet-smoke" +$profileRoot = Join-Path $root "profile-stylesheet-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8160 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "stylesheet_server.py" +$browserOut = Join-Path $root "stylesheet-auth.browser.stdout.txt" +$browserErr = Join-Path $root "stylesheet-auth.browser.stderr.txt" +$serverOut = Join-Path $root "stylesheet-auth.server.stdout.txt" +$serverErr = Join-Path $root "stylesheet-auth.server.stderr.txt" +$requestLog = Join-Path $root "stylesheet.requests.jsonl" +$pageUrl = "http://css%20user:p%40ss@127.0.0.1:$port/auth-stylesheet-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-stylesheet-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { + throw "localhost stylesheet auth server did not become ready" +} + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$loaded = $false +$cssEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $cssEntries = @($entries | Where-Object { $_.path -eq "/private.css" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded" }) + if ($cssEntries.Count -gt 0) { $cssEntry = $cssEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($cssEntry -and $loadedEntry) { + $loaded = $true + break + } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +for ($i = 0; $i -lt 20; $i++) { + if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break } + Start-Sleep -Milliseconds 100 +} +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + loaded = $loaded + stylesheet_allowed = if ($cssEntry) { [bool]$cssEntry.allowed } else { $false } + stylesheet_user_agent = if ($cssEntry) { [string]$cssEntry.user_agent } else { "" } + stylesheet_cookie = if ($cssEntry) { [string]$cssEntry.cookie } else { "" } + stylesheet_referer = if ($cssEntry) { [string]$cssEntry.referer } else { "" } + stylesheet_authorization = if ($cssEntry) { [string]$cssEntry.authorization } else { "" } + stylesheet_accept = if ($cssEntry) { [string]$cssEntry.accept } else { "" } + loaded_sheet = if ($loadedEntry) { [string]$loadedEntry.sheet } else { "" } + loaded_count = if ($loadedEntry) { [string]$loadedEntry.count } else { "" } + loaded_applied = if ($loadedEntry) { [string]$loadedEntry.applied } else { "" } + loaded_bg = if ($loadedEntry) { [string]$loadedEntry.bg } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-anonymous-probe.ps1 b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-anonymous-probe.ps1 new file mode 100644 index 000000000..e833b9ae8 --- /dev/null +++ b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-anonymous-probe.ps1 @@ -0,0 +1,91 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stylesheet-smoke" +$profileRoot = Join-Path $root "profile-stylesheet-import-anonymous" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8160 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "stylesheet_server.py" +$browserOut = Join-Path $root "stylesheet-import-anonymous.browser.stdout.txt" +$browserErr = Join-Path $root "stylesheet-import-anonymous.browser.stderr.txt" +$serverOut = Join-Path $root "stylesheet-import-anonymous.server.stdout.txt" +$serverErr = Join-Path $root "stylesheet-import-anonymous.server.stderr.txt" +$requestLog = Join-Path $root "stylesheet.requests.jsonl" +$pageUrl = "http://css%20user:p%40ss@127.0.0.1:$port/auth-stylesheet-import-anonymous-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-stylesheet-import-anonymous-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost stylesheet import anonymous server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$loaded = $false +$rootEntry = $null +$childEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $rootEntries = @($entries | Where-Object { $_.path -eq "/private-import-anonymous-root.css" }) + $childEntries = @($entries | Where-Object { $_.path -eq "/private-import-anonymous-child.css" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded-import-anon" }) + if ($rootEntries.Count -gt 0) { $rootEntry = $rootEntries[-1] } + if ($childEntries.Count -gt 0) { $childEntry = $childEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($rootEntry -and $childEntry -and $loadedEntry) { + $loaded = $true + break + } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + loaded = $loaded + root_allowed = if ($rootEntry) { [bool]$rootEntry.allowed } else { $false } + root_cookie = if ($rootEntry) { [string]$rootEntry.cookie } else { "" } + root_referer = if ($rootEntry) { [string]$rootEntry.referer } else { "" } + root_authorization = if ($rootEntry) { [string]$rootEntry.authorization } else { "" } + child_allowed = if ($childEntry) { [bool]$childEntry.allowed } else { $false } + child_cookie = if ($childEntry) { [string]$childEntry.cookie } else { "" } + child_referer = if ($childEntry) { [string]$childEntry.referer } else { "" } + child_authorization = if ($childEntry) { [string]$childEntry.authorization } else { "" } + loaded_applied = if ($loadedEntry) { [string]$loadedEntry.applied } else { "" } + loaded_bg = if ($loadedEntry) { [string]$loadedEntry.bg } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-auth-probe.ps1 b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-auth-probe.ps1 new file mode 100644 index 000000000..87808a839 --- /dev/null +++ b/tmp-browser-smoke/stylesheet-smoke/chrome-stylesheet-import-auth-probe.ps1 @@ -0,0 +1,91 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\stylesheet-smoke" +$profileRoot = Join-Path $root "profile-stylesheet-import-auth" +$appDataRoot = Join-Path $profileRoot "lightpanda" +$port = 8160 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$serverScript = Join-Path $root "stylesheet_server.py" +$browserOut = Join-Path $root "stylesheet-import-auth.browser.stdout.txt" +$browserErr = Join-Path $root "stylesheet-import-auth.browser.stderr.txt" +$serverOut = Join-Path $root "stylesheet-import-auth.server.stdout.txt" +$serverErr = Join-Path $root "stylesheet-import-auth.server.stderr.txt" +$requestLog = Join-Path $root "stylesheet.requests.jsonl" +$pageUrl = "http://css%20user:p%40ss@127.0.0.1:$port/auth-stylesheet-import-page.html" + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$requestLog -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot "browse-settings-v1.txt") -NoNewline + +$server = Start-Process -FilePath "python" -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr +$ready = $false +for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/auth-stylesheet-import-page.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} +} +if (-not $ready) { throw "localhost stylesheet import auth server did not become ready" } + +$browser = Start-Process -FilePath $browserExe -ArgumentList "browse",$pageUrl -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + +$loaded = $false +$rootEntry = $null +$childEntry = $null +$loadedEntry = $null +for ($i = 0; $i -lt 80; $i++) { + Start-Sleep -Milliseconds 250 + if (Test-Path $requestLog) { + $entries = Get-Content $requestLog | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object { $_ | ConvertFrom-Json } + $rootEntries = @($entries | Where-Object { $_.path -eq "/private-import-root.css" }) + $childEntries = @($entries | Where-Object { $_.path -eq "/private-import-child.css" }) + $loadedEntries = @($entries | Where-Object { $_.path -eq "/loaded-import" }) + if ($rootEntries.Count -gt 0) { $rootEntry = $rootEntries[-1] } + if ($childEntries.Count -gt 0) { $childEntry = $childEntries[-1] } + if ($loadedEntries.Count -gt 0) { $loadedEntry = $loadedEntries[-1] } + if ($rootEntry -and $childEntry -and $loadedEntry) { + $loaded = $true + break + } + } +} + +$serverMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +$browserMeta = Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate +if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force } +if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } +$browserGone = -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +$serverGone = -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) + +[ordered]@{ + server_pid = $server.Id + browser_pid = $browser.Id + ready = $ready + loaded = $loaded + root_allowed = if ($rootEntry) { [bool]$rootEntry.allowed } else { $false } + root_cookie = if ($rootEntry) { [string]$rootEntry.cookie } else { "" } + root_referer = if ($rootEntry) { [string]$rootEntry.referer } else { "" } + root_authorization = if ($rootEntry) { [string]$rootEntry.authorization } else { "" } + child_allowed = if ($childEntry) { [bool]$childEntry.allowed } else { $false } + child_cookie = if ($childEntry) { [string]$childEntry.cookie } else { "" } + child_referer = if ($childEntry) { [string]$childEntry.referer } else { "" } + child_authorization = if ($childEntry) { [string]$childEntry.authorization } else { "" } + loaded_applied = if ($loadedEntry) { [string]$loadedEntry.applied } else { "" } + loaded_bg = if ($loadedEntry) { [string]$loadedEntry.bg } else { "" } + loaded_allowed = if ($loadedEntry) { [bool]$loadedEntry.allowed } else { $false } + browser_meta = $browserMeta + server_meta = $serverMeta + browser_gone = $browserGone + server_gone = $serverGone +} | ConvertTo-Json -Depth 6 diff --git a/tmp-browser-smoke/stylesheet-smoke/stylesheet_server.py b/tmp-browser-smoke/stylesheet-smoke/stylesheet_server.py new file mode 100644 index 000000000..ed09c0e3b --- /dev/null +++ b/tmp-browser-smoke/stylesheet-smoke/stylesheet_server.py @@ -0,0 +1,449 @@ +import json +import sys +from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from urllib.parse import parse_qs, urlparse + + +ROOT = Path(__file__).resolve().parent +REQUEST_LOG = ROOT / "stylesheet.requests.jsonl" +PORT = 8160 + + +def append_log(entry: dict) -> None: + with REQUEST_LOG.open("a", encoding="utf-8") as fh: + fh.write(json.dumps(entry) + "\n") + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + if self.path == "/auth-stylesheet-page.html": + body = b""" + +Stylesheet Pending + + + + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpcss=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-stylesheet-anonymous-page.html": + body = b""" + +Stylesheet Pending + + + + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpcssanon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-stylesheet-import-page.html": + body = b""" + +Stylesheet Import Pending + + + + +""" + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpcssimport=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/auth-stylesheet-import-anonymous-page.html": + body = """ + +Stylesheet Import Anonymous Pending + + + + +""".replace( + "__ANON_IMPORT_ROOT__", + f"http://css%20user:p%40ss@127.0.0.1:{PORT}/private-import-anonymous-root.css", + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Set-Cookie", "lpcssimportanon=ok; Path=/") + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-stylesheet-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpcss=ok" in cookie + and referer == expected_referer + and authorization == "Basic Y3NzIHVzZXI6cEBzcw==" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"body { background-color: rgb(24, 194, 62); color: white; }" + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-anonymous.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-stylesheet-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"body { background-color: rgb(20, 122, 214); color: white; }" + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-import-root.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-stylesheet-import-page.html" + allowed = ( + "Lightpanda/" in ua + and "lpcssimport=ok" in cookie + and referer == expected_referer + and authorization == "Basic Y3NzIHVzZXI6cEBzcw==" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = ( + f'@import "http://css%20user:p%40ss@127.0.0.1:{PORT}/private-import-child.css"; ' + 'body { color: white; }' + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-import-child.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/private-import-root.css" + allowed = ( + "Lightpanda/" in ua + and "lpcssimport=ok" in cookie + and referer == expected_referer + and authorization == "Basic Y3NzIHVzZXI6cEBzcw==" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"body { background-color: rgb(55, 155, 45); }" + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-import-anonymous-root.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/auth-stylesheet-import-anonymous-page.html" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = ( + f'@import "http://css%20user:p%40ss@127.0.0.1:{PORT}/private-import-anonymous-child.css"; ' + 'body { color: white; }' + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == "/private-import-anonymous-child.css": + ua = self.headers.get("User-Agent", "") + cookie = self.headers.get("Cookie", "") + referer = self.headers.get("Referer", "") + authorization = self.headers.get("Authorization", "") + accept = self.headers.get("Accept", "") + expected_referer = f"http://127.0.0.1:{PORT}/private-import-anonymous-root.css" + allowed = ( + "Lightpanda/" in ua + and cookie == "" + and referer == expected_referer + and authorization == "" + and "text/css" in accept + ) + append_log({ + "path": self.path, + "user_agent": ua, + "cookie": cookie, + "referer": referer, + "authorization": authorization, + "accept": accept, + "allowed": allowed, + }) + if not allowed: + body = b"body{background:#ff0000;}" + self.send_response(403) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = b"body { background-color: rgb(25, 115, 205); }" + self.send_response(200) + self.send_header("Content-Type", "text/css; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path.startswith("/loaded"): + parsed = urlparse(self.path) + query = parse_qs(parsed.query) + append_log({ + "path": parsed.path, + "sheet": query.get("sheet", [""])[0], + "count": query.get("count", [""])[0], + "applied": query.get("applied", [""])[0], + "bg": query.get("bg", [""])[0], + "allowed": ( + query.get("sheet", ["0"])[0] == "1" + and query.get("count", ["0"])[0] != "0" + and query.get("applied", ["0"])[0] == "1" + ), + }) + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_response(404) + self.end_headers() + + def log_message(self, fmt: str, *args) -> None: + return + + +def main() -> int: + global PORT + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8160 + PORT = port + if REQUEST_LOG.exists(): + REQUEST_LOG.unlink() + server = ThreadingHTTPServer(("127.0.0.1", port), Handler) + print(f"READY {port}", flush=True) + try: + server.serve_forever() + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tmp-browser-smoke/tabs/TabProbeCommon.ps1 b/tmp-browser-smoke/tabs/TabProbeCommon.ps1 new file mode 100644 index 000000000..9a187f649 --- /dev/null +++ b/tmp-browser-smoke/tabs/TabProbeCommon.ps1 @@ -0,0 +1,83 @@ +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-TabProbeEnvironment([string]$ProfileRoot) { + return @{ + APPDATA = $ProfileRoot + LOCALAPPDATA = $ProfileRoot + } +} + +function Wait-TabWindowHandle([int]$ProcessId, [int]$Attempts = 60) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + $hwnd = Get-TabWindowHandle $ProcessId + if ($hwnd -ne [IntPtr]::Zero) { + return $hwnd + } + } + return [IntPtr]::Zero +} + +function Get-TabWindowHandle([int]$ProcessId) { + $proc = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + return [IntPtr]$proc.MainWindowHandle + } + return [IntPtr]::Zero +} + +function Wait-TabTitle([int]$ProcessId, [string]$Needle, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue + if (-not $proc -or $proc.MainWindowHandle -eq 0) { continue } + $title = Get-SmokeWindowTitle ([IntPtr]$proc.MainWindowHandle) + if ($title -like "*$Needle*") { + return $title + } + } + return $null +} + +function Get-TabClientPoint([int]$TabIndex, [int]$TabCount = 2, [switch]$Close, [switch]$New) { + $clientWidth = 960 + $presentationMargin = 12 + $findWidth = 300 + $tabGap = 4 + $tabNewWidth = 22 + $tabMaxWidth = 180 + $findLeft = [Math]::Max($presentationMargin + 120, ($clientWidth - $presentationMargin) - $findWidth) + $tabNewRight = $findLeft - $tabGap + $tabNewLeft = [Math]::Max($presentationMargin, $tabNewRight - $tabNewWidth) + if ($New) { + return @{ + X = $tabNewLeft + 10 + Y = 14 + } + } + + $gaps = ([Math]::Max($TabCount, 1) - 1) * $tabGap + $availableRight = $tabNewLeft - $tabGap + $availableWidth = [Math]::Max(1, $availableRight - $presentationMargin - $gaps) + $tabWidth = [Math]::Max(1, [Math]::Min($tabMaxWidth, [int][Math]::Truncate($availableWidth / [Math]::Max($TabCount, 1)))) + $left = $presentationMargin + ($TabIndex * ($tabWidth + $tabGap)) + if ($Close) { + return @{ + X = $left + $tabWidth - 13 + Y = 14 + } + } + return @{ + X = $left + [int][Math]::Max(8, [Math]::Min(36, [Math]::Floor($tabWidth / 2))) + Y = 14 + } +} + +function Stop-OwnedProbeProcess([System.Diagnostics.Process]$Process) { + if (-not $Process) { return $null } + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$($Process.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta -and $meta.CommandLine -and $meta.CommandLine -notmatch "codex\.js|@openai/codex") { + Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue + } + return $meta +} diff --git a/tmp-browser-smoke/tabs/chrome-duplicate-tab-probe.ps1 b/tmp-browser-smoke/tabs/chrome-duplicate-tab-probe.ps1 new file mode 100644 index 000000000..eeb8935fc --- /dev/null +++ b/tmp-browser-smoke/tabs/chrome-duplicate-tab-probe.ps1 @@ -0,0 +1,96 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\tabs" +$profileRoot = Join-Path $root "profile-duplicate" +$port = 8157 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-duplicate.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-duplicate.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-duplicate.server.stdout.txt" +$serverErr = Join-Path $root "chrome-duplicate.server.stderr.txt" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$duplicateWorked = $false +$switchBackWorked = $false +$switchForwardWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/duplicate-one.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "duplicate tab probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/duplicate-one.html","--window_width","960","--window_height","640" -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "duplicate tab probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Duplicate One" + if (-not $titles.initial) { throw "duplicate tab probe initial title missing" } + + Send-SmokeCtrlShiftD + Start-Sleep -Milliseconds 250 + [void](Invoke-SmokeClientClick $hwnd 160 40) + Start-Sleep -Milliseconds 120 + Send-SmokeText "http://127.0.0.1:$port/duplicate-two.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + + $titles.duplicate = Wait-TabTitle $browser.Id "Duplicate Two" + $duplicateWorked = [bool]$titles.duplicate + if (-not $duplicateWorked) { throw "duplicate tab probe did not navigate duplicated tab" } + + Send-SmokeCtrlShiftTab + $titles.back = Wait-TabTitle $browser.Id "Duplicate One" + $switchBackWorked = [bool]$titles.back + if (-not $switchBackWorked) { throw "duplicate tab probe did not switch back to original tab" } + + Send-SmokeCtrlTab + $titles.forward = Wait-TabTitle $browser.Id "Duplicate Two" + $switchForwardWorked = [bool]$titles.forward + if (-not $switchForwardWorked) { throw "duplicate tab probe did not switch forward to duplicated tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + duplicate_worked = $duplicateWorked + switch_back_worked = $switchBackWorked + switch_forward_worked = $switchForwardWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/tabs/chrome-reopen-closed-probe.ps1 b/tmp-browser-smoke/tabs/chrome-reopen-closed-probe.ps1 new file mode 100644 index 000000000..5b45193ba --- /dev/null +++ b/tmp-browser-smoke/tabs/chrome-reopen-closed-probe.ps1 @@ -0,0 +1,112 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\tabs" +$port = 8152 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$initialPng = Join-Path $root "chrome-reopen.initial.png" +$browserOut = Join-Path $root "chrome-reopen.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-reopen.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-reopen.server.stdout.txt" +$serverErr = Join-Path $root "chrome-reopen.server.stderr.txt" +$profileRoot = Join-Path $root "profile-reopen" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $initialPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\TabProbeCommon.ps1" + +$server = $null +$browser = $null +$ready = $false +$newTabWorked = $false +$navigateWorked = $false +$closeWorked = $false +$reopenWorked = $false +$screenshotReady = $false +$titles = [ordered]@{} +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "reopen probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640","--screenshot_png",$initialPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $initialPng) -and ((Get-Item $initialPng).Length -gt 0)) { + $screenshotReady = $true + break + } + } + if (-not $screenshotReady) { throw "reopen probe initial screenshot did not become ready" } + $hwnd = Wait-TabWindowHandle $browser.Id + if ($hwnd -eq [IntPtr]::Zero) { throw "reopen probe window handle not found" } + Show-SmokeWindow $hwnd + + $titles.initial = Wait-TabTitle $browser.Id "Tab One" + if (-not $titles.initial) { throw "initial tab title did not appear" } + + $newTabPoint = Get-TabClientPoint 0 -New + [void](Invoke-SmokeClientClick $hwnd $newTabPoint.X $newTabPoint.Y) + $titles.new_tab = Wait-TabTitle $browser.Id "New Tab" + $newTabWorked = [bool]$titles.new_tab + if (-not $newTabWorked) { throw "new tab did not open" } + + [void](Invoke-SmokeClientClick $hwnd 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeText "http://127.0.0.1:$port/two.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $titles.second = Wait-TabTitle $browser.Id "Tab Two" + $navigateWorked = [bool]$titles.second + if (-not $navigateWorked) { throw "second tab navigation did not complete" } + + Send-SmokeCtrlW + $titles.after_close = Wait-TabTitle $browser.Id "Tab One" + $closeWorked = [bool]$titles.after_close + if (-not $closeWorked) { throw "close tab shortcut did not return to tab one" } + + Send-SmokeCtrlShiftT + $titles.after_reopen = Wait-TabTitle $browser.Id "Tab Two" + $reopenWorked = [bool]$titles.after_reopen + if (-not $reopenWorked) { throw "reopen closed tab shortcut did not restore tab two" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + $browserMeta = Stop-OwnedProbeProcess $browser + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $screenshotReady + new_tab_worked = $newTabWorked + navigate_worked = $navigateWorked + close_worked = $closeWorked + reopen_worked = $reopenWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/tabs/chrome-session-restore-probe.ps1 b/tmp-browser-smoke/tabs/chrome-session-restore-probe.ps1 new file mode 100644 index 000000000..de418617d --- /dev/null +++ b/tmp-browser-smoke/tabs/chrome-session-restore-probe.ps1 @@ -0,0 +1,136 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\tabs" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$run1Png = Join-Path $root "chrome-restore.run1.png" +$run2Png = Join-Path $root "chrome-restore.run2.png" +$run1Out = Join-Path $root "chrome-restore.run1.browser.stdout.txt" +$run1Err = Join-Path $root "chrome-restore.run1.browser.stderr.txt" +$run2Out = Join-Path $root "chrome-restore.run2.browser.stdout.txt" +$run2Err = Join-Path $root "chrome-restore.run2.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-restore.server.stdout.txt" +$serverErr = Join-Path $root "chrome-restore.server.stderr.txt" +$profileRoot = Join-Path $root "profile-restore" + +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $profileRoot | Out-Null +Remove-Item $run1Png,$run2Png,$run1Out,$run1Err,$run2Out,$run2Err,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +$env:APPDATA = $profileRoot +$env:LOCALAPPDATA = $profileRoot + +. "$PSScriptRoot\TabProbeCommon.ps1" + +$server = $null +$browser1 = $null +$browser2 = $null +$ready = $false +$sessionPrepared = $false +$activeRestoreWorked = $false +$otherTabWorked = $false +$run1ScreenshotReady = $false +$run2ScreenshotReady = $false +$titles = [ordered]@{} +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "restore probe server did not become ready" } + + $browser1 = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640","--screenshot_png",$run1Png -WorkingDirectory $repo -PassThru -RedirectStandardOutput $run1Out -RedirectStandardError $run1Err + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $run1Png) -and ((Get-Item $run1Png).Length -gt 0)) { + $run1ScreenshotReady = $true + break + } + } + if (-not $run1ScreenshotReady) { throw "restore probe run1 screenshot did not become ready" } + $hwnd1 = Wait-TabWindowHandle $browser1.Id + if ($hwnd1 -eq [IntPtr]::Zero) { throw "restore probe run1 window handle not found" } + Show-SmokeWindow $hwnd1 + + $titles.run1_initial = Wait-TabTitle $browser1.Id "Tab One" + if (-not $titles.run1_initial) { throw "restore probe run1 initial title missing" } + + $newTabPoint = Get-TabClientPoint 0 -New + [void](Invoke-SmokeClientClick $hwnd1 $newTabPoint.X $newTabPoint.Y) + $titles.run1_new_tab = Wait-TabTitle $browser1.Id "New Tab" + if (-not $titles.run1_new_tab) { throw "restore probe run1 new tab missing" } + + [void](Invoke-SmokeClientClick $hwnd1 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeText "http://127.0.0.1:$port/two.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $titles.run1_second = Wait-TabTitle $browser1.Id "Tab Two" + if (-not $titles.run1_second) { throw "restore probe run1 second tab navigation missing" } + $sessionPrepared = $true + + $browser1Meta = Stop-OwnedProbeProcess $browser1 + Start-Sleep -Milliseconds 300 + if (Get-Process -Id $browser1.Id -ErrorAction SilentlyContinue) { throw "restore probe run1 browser did not exit" } + + $browser2 = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640","--screenshot_png",$run2Png -WorkingDirectory $repo -PassThru -RedirectStandardOutput $run2Out -RedirectStandardError $run2Err + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $run2Png) -and ((Get-Item $run2Png).Length -gt 0)) { + $run2ScreenshotReady = $true + break + } + } + if (-not $run2ScreenshotReady) { throw "restore probe run2 screenshot did not become ready" } + $hwnd2 = Wait-TabWindowHandle $browser2.Id + if ($hwnd2 -eq [IntPtr]::Zero) { throw "restore probe run2 window handle not found" } + Show-SmokeWindow $hwnd2 + + $titles.run2_active = Wait-TabTitle $browser2.Id "Tab Two" + $activeRestoreWorked = [bool]$titles.run2_active + if (-not $activeRestoreWorked) { throw "restore probe did not reopen the last active tab" } + + Send-SmokeCtrlShiftTab + $titles.run2_other = Wait-TabTitle $browser2.Id "Tab One" + $otherTabWorked = [bool]$titles.run2_other + if (-not $otherTabWorked) { throw "restore probe did not restore the other saved tab" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = Stop-OwnedProbeProcess $server + if (-not $browser1Meta) { $browser1Meta = Stop-OwnedProbeProcess $browser1 } + $browser2Meta = Stop-OwnedProbeProcess $browser2 + Start-Sleep -Milliseconds 200 + $browser1Gone = if ($browser1) { -not (Get-Process -Id $browser1.Id -ErrorAction SilentlyContinue) } else { $true } + $browser2Gone = if ($browser2) { -not (Get-Process -Id $browser2.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + run1_browser_pid = if ($browser1) { $browser1.Id } else { 0 } + run2_browser_pid = if ($browser2) { $browser2.Id } else { 0 } + ready = $ready + run1_screenshot_ready = $run1ScreenshotReady + run2_screenshot_ready = $run2ScreenshotReady + session_prepared = $sessionPrepared + active_restore_worked = $activeRestoreWorked + other_tab_worked = $otherTabWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser1_meta = $browser1Meta + browser2_meta = $browser2Meta + browser1_gone = $browser1Gone + browser2_gone = $browser2Gone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/tabs/chrome-tabs-probe.ps1 b/tmp-browser-smoke/tabs/chrome-tabs-probe.ps1 new file mode 100644 index 000000000..274df9506 --- /dev/null +++ b/tmp-browser-smoke/tabs/chrome-tabs-probe.ps1 @@ -0,0 +1,188 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\tabs" +$port = 8151 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$initialPng = Join-Path $root "tabs-initial.png" +$browserOut = Join-Path $root "chrome-tabs.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-tabs.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-tabs.server.stdout.txt" +$serverErr = Join-Path $root "chrome-tabs.server.stderr.txt" +Remove-Item $initialPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Wait-ForTitle([int]$ProcessId, [string]$Needle, [int]$Attempts = 40) { + for ($i = 0; $i -lt $Attempts; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue + if (-not $proc -or $proc.MainWindowHandle -eq 0) { continue } + $title = Get-SmokeWindowTitle ([IntPtr]$proc.MainWindowHandle) + if ($title -like "*$Needle*") { + return $title + } + } + return $null +} + +function Get-ClientPoint([int]$TabIndex, [switch]$Close, [switch]$New) { + $clientWidth = 960 + $presentationMargin = 12 + $findWidth = 300 + $tabGap = 4 + $tabNewWidth = 22 + $tabMaxWidth = 180 + $findLeft = [Math]::Max($presentationMargin + 120, ($clientWidth - $presentationMargin) - $findWidth) + $tabNewRight = $findLeft - $tabGap + $tabNewLeft = [Math]::Max($presentationMargin, $tabNewRight - $tabNewWidth) + if ($New) { + return @{ + X = $tabNewLeft + 10 + Y = 14 + } + } + $availableRight = $tabNewLeft - $tabGap + $tabCount = 2 + $gaps = ($tabCount - 1) * $tabGap + $availableWidth = [Math]::Max(1, $availableRight - $presentationMargin - $gaps) + $tabWidth = [Math]::Max(1, [Math]::Min($tabMaxWidth, [int][Math]::Truncate($availableWidth / $tabCount))) + $left = $presentationMargin + ($TabIndex * ($tabWidth + $tabGap)) + if ($Close) { + return @{ + X = $left + $tabWidth - 13 + Y = 14 + } + } + return @{ + X = $left + [int][Math]::Max(8, [Math]::Min(36, [Math]::Floor($tabWidth / 2))) + Y = 14 + } +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$newTabWorked = $false +$addressNavigateWorked = $false +$keyboardBackWorked = $false +$keyboardForwardWorked = $false +$clickSwitchWorked = $false +$closeWorked = $false +$failure = $null +$titles = [ordered]@{} + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "tab probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","960","--window_height","640","--screenshot_png",$initialPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $initialPng) -and ((Get-Item $initialPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "tab probe initial screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "tab probe window handle not found" } + + Show-SmokeWindow $hwnd + $titles.initial = Wait-ForTitle $browser.Id "Tab One" + if (-not $titles.initial) { throw "initial tab title did not appear" } + + $newTabPoint = Get-ClientPoint 0 -New + [void](Invoke-SmokeClientClick $hwnd $newTabPoint.X $newTabPoint.Y) + $titles.new_tab = Wait-ForTitle $browser.Id "New Tab" + if (-not $titles.new_tab) { + Start-Sleep -Milliseconds 400 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $titles.new_tab_current = Get-SmokeWindowTitle ([IntPtr]$proc.MainWindowHandle) + } + } + $newTabWorked = [bool]$titles.new_tab + if (-not $newTabWorked) { throw "new tab button did not open a blank tab" } + + [void](Invoke-SmokeClientClick $hwnd 160 40) + Start-Sleep -Milliseconds 150 + Send-SmokeText "http://127.0.0.1:$port/two.html" + Start-Sleep -Milliseconds 100 + Send-SmokeEnter + $titles.second = Wait-ForTitle $browser.Id "Tab Two" + $addressNavigateWorked = [bool]$titles.second + if (-not $addressNavigateWorked) { throw "new tab address bar navigation did not reach tab two" } + + Send-SmokeCtrlShiftTab + $titles.back = Wait-ForTitle $browser.Id "Tab One" + $keyboardBackWorked = [bool]$titles.back + if (-not $keyboardBackWorked) { throw "Ctrl+Shift+Tab did not return to tab one" } + + Send-SmokeCtrlTab + $titles.forward = Wait-ForTitle $browser.Id "Tab Two" + $keyboardForwardWorked = [bool]$titles.forward + if (-not $keyboardForwardWorked) { throw "Ctrl+Tab did not return to tab two" } + + $firstTabPoint = Get-ClientPoint 0 + [void](Invoke-SmokeClientClick $hwnd $firstTabPoint.X $firstTabPoint.Y) + $titles.click_switch = Wait-ForTitle $browser.Id "Tab One" + $clickSwitchWorked = [bool]$titles.click_switch + if (-not $clickSwitchWorked) { throw "tab strip click did not activate the first tab" } + + $secondTabPoint = Get-ClientPoint 1 + [void](Invoke-SmokeClientClick $hwnd $secondTabPoint.X $secondTabPoint.Y) + $titles.reopen_second = Wait-ForTitle $browser.Id "Tab Two" + if (-not $titles.reopen_second) { throw "second tab click did not reactivate tab two before close" } + + $closePoint = Get-ClientPoint 1 -Close + [void](Invoke-SmokeClientClick $hwnd $closePoint.X $closePoint.Y) + $titles.after_close = Wait-ForTitle $browser.Id "Tab One" + $closeWorked = [bool]$titles.after_close + if (-not $closeWorked) { throw "tab close button did not return to tab one" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + new_tab_worked = $newTabWorked + address_navigate_worked = $addressNavigateWorked + keyboard_back_worked = $keyboardBackWorked + keyboard_forward_worked = $keyboardForwardWorked + click_switch_worked = $clickSwitchWorked + close_worked = $closeWorked + titles = $titles + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/tabs/duplicate-one.html b/tmp-browser-smoke/tabs/duplicate-one.html new file mode 100644 index 000000000..668249c93 --- /dev/null +++ b/tmp-browser-smoke/tabs/duplicate-one.html @@ -0,0 +1,13 @@ + + + + + Duplicate One + + +
+

Duplicate One

+

This page is used to verify duplicate-tab behavior.

+
+ + diff --git a/tmp-browser-smoke/tabs/duplicate-two.html b/tmp-browser-smoke/tabs/duplicate-two.html new file mode 100644 index 000000000..b99a9d670 --- /dev/null +++ b/tmp-browser-smoke/tabs/duplicate-two.html @@ -0,0 +1,13 @@ + + + + + Duplicate Two + + +
+

Duplicate Two

+

This page confirms the duplicated tab can navigate independently.

+
+ + diff --git a/tmp-browser-smoke/tabs/index.html b/tmp-browser-smoke/tabs/index.html new file mode 100644 index 000000000..00ff87018 --- /dev/null +++ b/tmp-browser-smoke/tabs/index.html @@ -0,0 +1,15 @@ + + + + + Tab One + + + +

Tab One

+

First tab content

+ + diff --git a/tmp-browser-smoke/tabs/two.html b/tmp-browser-smoke/tabs/two.html new file mode 100644 index 000000000..72383287a --- /dev/null +++ b/tmp-browser-smoke/tabs/two.html @@ -0,0 +1,15 @@ + + + + + Tab Two + + + +

Tab Two

+

Second tab content

+ + diff --git a/tmp-browser-smoke/websocket-smoke/chrome-websocket-binary-close-probe.ps1 b/tmp-browser-smoke/websocket-smoke/chrome-websocket-binary-close-probe.ps1 new file mode 100644 index 000000000..60591480f --- /dev/null +++ b/tmp-browser-smoke/websocket-smoke/chrome-websocket-binary-close-probe.ps1 @@ -0,0 +1,116 @@ +$ErrorActionPreference = 'Stop' + +$root = 'C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\websocket-smoke' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$browserExe = if ($env:LIGHTPANDA_BROWSER_EXE) { $env:LIGHTPANDA_BROWSER_EXE } else { Join-Path $repo 'zig-out\bin\lightpanda.exe' } +$serverScript = Join-Path $root 'websocket_server.py' +$browserOut = Join-Path $root 'websocket-binary-close.browser.stdout.txt' +$browserErr = Join-Path $root 'websocket-binary-close.browser.stderr.txt' +$serverOut = Join-Path $root 'websocket-binary-close.server.stdout.txt' +$serverErr = Join-Path $root 'websocket-binary-close.server.stderr.txt' +$profileRoot = Join-Path $root 'profile-websocket-binary-close' +$appDataRoot = Join-Path $profileRoot 'lightpanda' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot 'browse-settings-v1.txt') -NoNewline + +function Get-FreePort { + $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) + $listener.Start() + try { return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port } finally { $listener.Stop() } +} + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return '' +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch 'codex\.js|@openai/codex') { + try { Stop-Process -Id $TargetPid -Force -ErrorAction Stop } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +$port = Get-FreePort +$pageUrl = "http://127.0.0.1:$port/index.html?mode=binary-close" +$server = $null +$browser = $null +$ready = $false +$titleReady = $false +$failure = $null + +try { + $server = Start-Process -FilePath 'python' -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri $pageUrl -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw 'websocket binary-close server did not become ready' } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList 'browse',$pageUrl,'--window_width','840','--window_height','560' -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if (-not $proc) { break } + if ($proc.MainWindowHandle -eq 0) { continue } + if ($proc.MainWindowTitle -like '*WebSocket Binary Close Ready*') { + $titleReady = $true + break + } + if ($proc.MainWindowTitle -like '*WebSocket Binary Close Error*') { + break + } + } + + if (-not $titleReady) { + $lastTitle = (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue).MainWindowTitle + throw "websocket binary-close page did not reach ready title; last title: $lastTitle" + } +} catch { + $failure = $_.Exception.Message +} finally { + if ($browser) { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + if ($server) { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + + $result = [ordered]@{ + ready = $ready + title_ready = $titleReady + browser_pid = if ($browser) { $browser.Id } else { 0 } + server_pid = if ($server) { $server.Id } else { 0 } + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + error = if ($failure) { $failure } else { '' } + browser_stderr = if (Test-Path $browserErr) { (Get-Content $browserErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + server_stderr = if (Test-Path $serverErr) { (Get-Content $serverErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + } + $result | ConvertTo-Json -Depth 5 + + if ($failure -or -not $ready -or -not $titleReady) { + exit 1 + } +} diff --git a/tmp-browser-smoke/websocket-smoke/chrome-websocket-echo-probe.ps1 b/tmp-browser-smoke/websocket-smoke/chrome-websocket-echo-probe.ps1 new file mode 100644 index 000000000..52b635121 --- /dev/null +++ b/tmp-browser-smoke/websocket-smoke/chrome-websocket-echo-probe.ps1 @@ -0,0 +1,116 @@ +$ErrorActionPreference = 'Stop' + +$root = 'C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\websocket-smoke' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$browserExe = if ($env:LIGHTPANDA_BROWSER_EXE) { $env:LIGHTPANDA_BROWSER_EXE } else { Join-Path $repo 'zig-out\bin\lightpanda.exe' } +$serverScript = Join-Path $root 'websocket_server.py' +$browserOut = Join-Path $root 'websocket-echo.browser.stdout.txt' +$browserErr = Join-Path $root 'websocket-echo.browser.stderr.txt' +$serverOut = Join-Path $root 'websocket-echo.server.stdout.txt' +$serverErr = Join-Path $root 'websocket-echo.server.stderr.txt' +$profileRoot = Join-Path $root 'profile-websocket-echo' +$appDataRoot = Join-Path $profileRoot 'lightpanda' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot 'browse-settings-v1.txt') -NoNewline + +function Get-FreePort { + $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) + $listener.Start() + try { return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port } finally { $listener.Stop() } +} + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return '' +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch 'codex\.js|@openai/codex') { + try { Stop-Process -Id $TargetPid -Force -ErrorAction Stop } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +$port = Get-FreePort +$pageUrl = "http://127.0.0.1:$port/index.html" +$server = $null +$browser = $null +$ready = $false +$titleReady = $false +$failure = $null + +try { + $server = Start-Process -FilePath 'python' -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri $pageUrl -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw 'websocket echo server did not become ready' } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList 'browse',$pageUrl,'--window_width','840','--window_height','560' -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if (-not $proc) { break } + if ($proc.MainWindowHandle -eq 0) { continue } + if ($proc.MainWindowTitle -like '*WebSocket Echo Ready*') { + $titleReady = $true + break + } + if ($proc.MainWindowTitle -like '*WebSocket Echo Error*') { + break + } + } + + if (-not $titleReady) { + $lastTitle = (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue).MainWindowTitle + throw "websocket echo page did not reach ready title; last title: $lastTitle" + } +} catch { + $failure = $_.Exception.Message +} finally { + if ($browser) { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + if ($server) { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + + $result = [ordered]@{ + ready = $ready + title_ready = $titleReady + browser_pid = if ($browser) { $browser.Id } else { 0 } + server_pid = if ($server) { $server.Id } else { 0 } + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + error = if ($failure) { $failure } else { '' } + browser_stderr = if (Test-Path $browserErr) { (Get-Content $browserErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + server_stderr = if (Test-Path $serverErr) { (Get-Content $serverErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + } + $result | ConvertTo-Json -Depth 5 + + if ($failure -or -not $ready -or -not $titleReady) { + exit 1 + } +} diff --git a/tmp-browser-smoke/websocket-smoke/chrome-websocket-subprotocol-probe.ps1 b/tmp-browser-smoke/websocket-smoke/chrome-websocket-subprotocol-probe.ps1 new file mode 100644 index 000000000..29a9cf7ce --- /dev/null +++ b/tmp-browser-smoke/websocket-smoke/chrome-websocket-subprotocol-probe.ps1 @@ -0,0 +1,116 @@ +$ErrorActionPreference = 'Stop' + +$root = 'C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\websocket-smoke' +$repo = 'C:\Users\adyba\src\lightpanda-browser' +$browserExe = if ($env:LIGHTPANDA_BROWSER_EXE) { $env:LIGHTPANDA_BROWSER_EXE } else { Join-Path $repo 'zig-out\bin\lightpanda.exe' } +$serverScript = Join-Path $root 'websocket_server.py' +$browserOut = Join-Path $root 'websocket-subprotocol.browser.stdout.txt' +$browserErr = Join-Path $root 'websocket-subprotocol.browser.stderr.txt' +$serverOut = Join-Path $root 'websocket-subprotocol.server.stdout.txt' +$serverErr = Join-Path $root 'websocket-subprotocol.server.stderr.txt' +$profileRoot = Join-Path $root 'profile-websocket-subprotocol' +$appDataRoot = Join-Path $profileRoot 'lightpanda' + +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +cmd /c "rmdir /s /q `"$profileRoot`"" | Out-Null +New-Item -ItemType Directory -Force -Path $appDataRoot | Out-Null +@" +lightpanda-browse-settings-v1 +restore_previous_session 0 +allow_script_popups 0 +default_zoom_percent 100 +homepage_url +"@ | Set-Content -Path (Join-Path $appDataRoot 'browse-settings-v1.txt') -NoNewline + +function Get-FreePort { + $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) + $listener.Start() + try { return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port } finally { $listener.Stop() } +} + +function Get-ProcessCommandLine($TargetPid) { + $meta = Get-CimInstance Win32_Process -Filter "ProcessId=$TargetPid" -ErrorAction SilentlyContinue | Select-Object Name,ProcessId,CommandLine,CreationDate + if ($meta) { return [string]$meta.CommandLine } + return '' +} + +function Stop-VerifiedProcess($TargetPid) { + $cmd = Get-ProcessCommandLine $TargetPid + if ($cmd -and $cmd -notmatch 'codex\.js|@openai/codex') { + try { Stop-Process -Id $TargetPid -Force -ErrorAction Stop } catch { + if (Get-Process -Id $TargetPid -ErrorAction SilentlyContinue) { throw } + } + } +} + +$port = Get-FreePort +$pageUrl = "http://127.0.0.1:$port/index.html?mode=subprotocol" +$server = $null +$browser = $null +$ready = $false +$titleReady = $false +$failure = $null + +try { + $server = Start-Process -FilePath 'python' -ArgumentList $serverScript,$port -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri $pageUrl -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw 'websocket subprotocol server did not become ready' } + + $env:APPDATA = $profileRoot + $env:LOCALAPPDATA = $profileRoot + $browser = Start-Process -FilePath $browserExe -ArgumentList 'browse',$pageUrl,'--window_width','840','--window_height','560' -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if (-not $proc) { break } + if ($proc.MainWindowHandle -eq 0) { continue } + if ($proc.MainWindowTitle -like '*WebSocket Subprotocol Ready*') { + $titleReady = $true + break + } + if ($proc.MainWindowTitle -like '*WebSocket Subprotocol Error*') { + break + } + } + + if (-not $titleReady) { + $lastTitle = (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue).MainWindowTitle + throw "websocket subprotocol page did not reach ready title; last title: $lastTitle" + } +} catch { + $failure = $_.Exception.Message +} finally { + if ($browser) { + Stop-VerifiedProcess $browser.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + if ($server) { + Stop-VerifiedProcess $server.Id + for ($i = 0; $i -lt 20; $i++) { if (-not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Milliseconds 100 } + } + + $result = [ordered]@{ + ready = $ready + title_ready = $titleReady + browser_pid = if ($browser) { $browser.Id } else { 0 } + server_pid = if ($server) { $server.Id } else { 0 } + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + error = if ($failure) { $failure } else { '' } + browser_stderr = if (Test-Path $browserErr) { (Get-Content $browserErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + server_stderr = if (Test-Path $serverErr) { (Get-Content $serverErr -Raw) -replace "`r","\\r" -replace "`n","\\n" } else { '' } + } + $result | ConvertTo-Json -Depth 5 + + if ($failure -or -not $ready -or -not $titleReady) { + exit 1 + } +} diff --git a/tmp-browser-smoke/websocket-smoke/index.html b/tmp-browser-smoke/websocket-smoke/index.html new file mode 100644 index 000000000..a1e77e972 --- /dev/null +++ b/tmp-browser-smoke/websocket-smoke/index.html @@ -0,0 +1,214 @@ + + + + + WebSocket Echo Loading + + + +
+

WebSocket Echo

+
connecting...
+
+ + + diff --git a/tmp-browser-smoke/websocket-smoke/websocket_server.py b/tmp-browser-smoke/websocket-smoke/websocket_server.py new file mode 100644 index 000000000..6e35c3f09 --- /dev/null +++ b/tmp-browser-smoke/websocket-smoke/websocket_server.py @@ -0,0 +1,209 @@ +import base64 +import hashlib +import os +import socketserver +import sys +import urllib.parse + + +ROOT = os.path.dirname(__file__) +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8172 +GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +def recv_until(sock, marker): + data = b"" + while marker not in data: + chunk = sock.recv(4096) + if not chunk: + return None + data += chunk + if len(data) > 65536: + return None + return data + + +def read_frame(sock, initial): + data = initial + while len(data) < 2: + chunk = sock.recv(4096) + if not chunk: + return None, b"" + data += chunk + + b1, b2 = data[0], data[1] + opcode = b1 & 0x0F + masked = (b2 & 0x80) != 0 + length = b2 & 0x7F + offset = 2 + + if length == 126: + while len(data) < offset + 2: + chunk = sock.recv(4096) + if not chunk: + return None, b"" + data += chunk + length = int.from_bytes(data[offset:offset + 2], "big") + offset += 2 + elif length == 127: + while len(data) < offset + 8: + chunk = sock.recv(4096) + if not chunk: + return None, b"" + data += chunk + length = int.from_bytes(data[offset:offset + 8], "big") + offset += 8 + + mask = b"" + if masked: + while len(data) < offset + 4: + chunk = sock.recv(4096) + if not chunk: + return None, b"" + data += chunk + mask = data[offset:offset + 4] + offset += 4 + + while len(data) < offset + length: + chunk = sock.recv(4096) + if not chunk: + return None, b"" + data += chunk + + payload = bytearray(data[offset:offset + length]) + if masked: + for i in range(length): + payload[i] ^= mask[i % 4] + + return (opcode, bytes(payload)), data[offset + length:] + + +def send_frame(sock, opcode, payload): + header = bytearray([0x80 | opcode]) + length = len(payload) + if length <= 125: + header.append(length) + elif length <= 0xFFFF: + header.append(126) + header.extend(length.to_bytes(2, "big")) + else: + header.append(127) + header.extend(length.to_bytes(8, "big")) + sock.sendall(bytes(header) + payload) + + +class Handler(socketserver.BaseRequestHandler): + def handle(self): + self.request.settimeout(5) + raw = recv_until(self.request, b"\r\n\r\n") + if not raw: + return + + header_block, remainder = raw.split(b"\r\n\r\n", 1) + lines = header_block.decode("iso-8859-1").split("\r\n") + if not lines: + return + + parts = lines[0].split(" ") + if len(parts) < 2: + return + + method = parts[0] + raw_path = parts[1] + path = urllib.parse.urlsplit(raw_path).path + headers = {} + for line in lines[1:]: + if ":" not in line: + continue + name, value = line.split(":", 1) + headers[name.strip().lower()] = value.strip() + + if headers.get("upgrade", "").lower() == "websocket" and path in ("/echo", "/protocol"): + self.handle_websocket(path, headers, remainder) + return + + if method != "GET": + self.send_response(405, b"Method Not Allowed", b"text/plain; charset=utf-8") + return + + if path in ("/", "/index.html"): + with open(os.path.join(ROOT, "index.html"), "rb") as fh: + body = fh.read() + self.send_response(200, body, b"text/html; charset=utf-8") + return + + self.send_response(404, b"Not Found", b"text/plain; charset=utf-8") + + def send_response(self, status, body, content_type): + reason = { + 200: "OK", + 404: "Not Found", + 405: "Method Not Allowed", + }.get(status, "OK") + head = ( + f"HTTP/1.1 {status} {reason}\r\n" + f"Content-Length: {len(body)}\r\n" + f"Content-Type: {content_type.decode('ascii')}\r\n" + "Connection: close\r\n" + "\r\n" + ).encode("ascii") + self.request.sendall(head + body) + + def handle_websocket(self, path, headers, remainder): + key = headers.get("sec-websocket-key") + if not key: + return + + protocol_header = headers.get("sec-websocket-protocol", "") + selected_protocol = "" + if "superchat" in [part.strip() for part in protocol_header.split(",") if part.strip()]: + selected_protocol = "superchat" + + accept = base64.b64encode(hashlib.sha1((key + GUID).encode("ascii")).digest()).decode("ascii") + response = ( + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {accept}\r\n" + ) + if selected_protocol: + response += f"Sec-WebSocket-Protocol: {selected_protocol}\r\n" + if path == "/protocol": + response += "Sec-WebSocket-Extensions: permessage-test\r\n" + response += "\r\n" + response = response.encode("ascii") + self.request.sendall(response) + + pending = remainder + while True: + frame, pending = read_frame(self.request, pending) + if frame is None: + return + opcode, payload = frame + if opcode == 0x8: + try: + send_frame(self.request, 0x8, payload) + except OSError: + pass + return + if opcode == 0x9: + send_frame(self.request, 0xA, payload) + continue + if opcode == 0x1: + if payload == b"close-me": + close_payload = (4001).to_bytes(2, "big") + b"server-close" + send_frame(self.request, 0x8, close_payload) + return + send_frame(self.request, 0x1, b"echo:" + payload) + continue + if opcode == 0x2: + send_frame(self.request, 0x2, payload) + + +class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + allow_reuse_address = True + daemon_threads = True + + +with ThreadingTCPServer(("127.0.0.1", PORT), Handler) as httpd: + httpd.serve_forever() diff --git a/tmp-browser-smoke/wrapped-link/addressbar-probe.ps1 b/tmp-browser-smoke/wrapped-link/addressbar-probe.ps1 new file mode 100644 index 000000000..decd1b382 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/addressbar-probe.ps1 @@ -0,0 +1,109 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8144 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "addressbar.browser.stdout.txt" +$browserErr = Join-Path $root "addressbar.browser.stderr.txt" +$serverOut = Join-Path $root "addressbar.server.stdout.txt" +$serverErr = Join-Path $root "addressbar.server.stderr.txt" +$png = Join-Path $root "addressbar.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$png -Force -ErrorAction SilentlyContinue + +. (Join-Path (Split-Path $PSScriptRoot -Parent) "common\Win32Input.ps1") + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$navigated = $false +$titleBefore = $null +$titleAfter = $null +$serverSawNext = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "addressbar probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$png -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $png) -and ((Get-Item $png).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "addressbar screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "addressbar window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + [void](Invoke-SmokeClientClick $hwnd 120 40) + Start-Sleep -Milliseconds 200 + Send-SmokeCtrlA + Start-Sleep -Milliseconds 100 + Send-SmokeText "http://127.0.0.1:$port/next.html" + Start-Sleep -Milliseconds 250 + Send-SmokeEnter + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Wrapped Link Target*") { + $navigated = $true + break + } + } + if (-not $navigated -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $navigated = $true + } + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + title_before = $titleBefore + title_after = $titleAfter + navigated = $navigated + server_saw_next = $serverSawNext + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/blank-click-probe.ps1 b/tmp-browser-smoke/wrapped-link/blank-click-probe.ps1 new file mode 100644 index 000000000..1ce4241e1 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/blank-click-probe.ps1 @@ -0,0 +1,120 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8145 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "blank.browser.stdout.txt" +$browserErr = Join-Path $root "blank.browser.stderr.txt" +$serverOut = Join-Path $root "blank.server.stdout.txt" +$serverErr = Join-Path $root "blank.server.stderr.txt" +$png = Join-Path $root "blank.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$png -Force -ErrorAction SilentlyContinue + +Add-Type @" +using System; +using System.Runtime.InteropServices; +public static class BlankClickUser32 { + [StructLayout(LayoutKind.Sequential)] + public struct POINT { + public int X; + public int Y; + } + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + public static extern bool SetCursorPos(int X, int Y); + + [DllImport("user32.dll")] + public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo); +} +"@ + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$failure = $null +$aliveAfterClick = $false +$clickPoint = New-Object BlankClickUser32+POINT +$clickClientX = 120 +$clickClientY = 260 + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "blank-click probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$png -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $png) -and ((Get-Item $png).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "blank-click screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "blank-click window handle not found" } + + [void][BlankClickUser32]::ShowWindow($hwnd, 5) + [void][BlankClickUser32]::SetForegroundWindow($hwnd) + Start-Sleep -Milliseconds 250 + $clickPoint.X = $clickClientX + $clickPoint.Y = $clickClientY + [void][BlankClickUser32]::ClientToScreen($hwnd, [ref]$clickPoint) + [void][BlankClickUser32]::SetCursorPos($clickPoint.X, $clickPoint.Y) + Start-Sleep -Milliseconds 100 + [BlankClickUser32]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) + Start-Sleep -Milliseconds 80 + [BlankClickUser32]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero) + + Start-Sleep -Milliseconds 1500 + $aliveAfterClick = [bool](Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + click_client = [ordered]@{ x = $clickClientX; y = $clickClientY } + click_screen = [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } + alive_after_click = $aliveAfterClick + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/chrome-history-close-probe.ps1 b/tmp-browser-smoke/wrapped-link/chrome-history-close-probe.ps1 new file mode 100644 index 000000000..00ce996af --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/chrome-history-close-probe.ps1 @@ -0,0 +1,132 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\wrapped-link" +$port = 8154 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$readyPng = Join-Path $root "chrome-history-close.ready.png" +$browserOut = Join-Path $root "chrome-history-close.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-history-close.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-history-close.server.stdout.txt" +$serverErr = Join-Path $root "chrome-history-close.server.stderr.txt" +Remove-Item $readyPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Count-Hits([string]$Pattern) { + if (-not (Test-Path $serverErr)) { return 0 } + return ([regex]::Matches((Get-Content $serverErr -Raw), $Pattern)).Count +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$closeWorked = $false +$navigateWorked = $false +$initialNextHits = 0 +$afterNextHits = 0 +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "history close probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$readyPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $readyPng) -and ((Get-Item $readyPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "history close probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "history close probe window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($readyPng) + try { + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $blue.min_x) { throw "history close probe could not find wrapped link" } + + $linkX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $linkY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + $initialNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + Send-SmokeCtrlH + Start-Sleep -Milliseconds 250 + [void](Invoke-SmokeClientClick $hwnd 206 115) + Start-Sleep -Milliseconds 250 + $closeWorked = $true + + Show-SmokeWindow $hwnd + [void](Invoke-SmokeClientClick $hwnd $linkX $linkY) + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + if ($afterNextHits -gt $initialNextHits) { + $navigateWorked = $true + break + } + } + if (-not $navigateWorked) { throw "history close probe did not allow page navigation after close button" } +} catch { + $failure = $_.Exception.Message +} finally { + if ($browser) { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($server) { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 250 + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + close_worked = $closeWorked + navigate_worked = $navigateWorked + initial_next_hits = $initialNextHits + after_next_hits = $afterNextHits + error = $failure + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + } | ConvertTo-Json -Depth 6 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/chrome-history-keyboard-probe.ps1 b/tmp-browser-smoke/wrapped-link/chrome-history-keyboard-probe.ps1 new file mode 100644 index 000000000..cc8ee4ca3 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/chrome-history-keyboard-probe.ps1 @@ -0,0 +1,149 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\wrapped-link" +$port = 8153 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$readyPng = Join-Path $root "chrome-history-keyboard.ready.png" +$browserOut = Join-Path $root "chrome-history-keyboard.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-history-keyboard.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-history-keyboard.server.stdout.txt" +$serverErr = Join-Path $root "chrome-history-keyboard.server.stderr.txt" +Remove-Item $readyPng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Count-Hits([string]$Pattern) { + if (-not (Test-Path $serverErr)) { return 0 } + return ([regex]::Matches((Get-Content $serverErr -Raw), $Pattern)).Count +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$linkWorked = $false +$overlayWorked = $false +$initialIndexHits = 0 +$initialNextHits = 0 +$afterLinkNextHits = 0 +$afterOverlayIndexHits = 0 +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "history keyboard probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$readyPng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $readyPng) -and ((Get-Item $readyPng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "history keyboard probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "history keyboard probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + + $initialIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + $initialNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + + $bmp = [System.Drawing.Bitmap]::new($readyPng) + try { + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $blue.min_x) { throw "history keyboard probe could not find wrapped link" } + + $linkX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $linkY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + [void](Invoke-SmokeClientClick $hwnd $linkX $linkY) + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterLinkNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + if ($afterLinkNextHits -gt $initialNextHits) { + $linkWorked = $true + break + } + } + if (-not $linkWorked) { throw "history keyboard probe did not reach next page before opening overlay" } + + Start-Sleep -Milliseconds 1200 + Show-SmokeWindow $hwnd + Send-SmokeCtrlH + Start-Sleep -Milliseconds 250 + Send-SmokeUp + Start-Sleep -Milliseconds 120 + Send-SmokeEnter + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterOverlayIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + if ($afterOverlayIndexHits -gt $initialIndexHits) { + $overlayWorked = $true + break + } + } + if (-not $overlayWorked) { throw "history keyboard probe did not traverse back to the selected history row" } +} catch { + $failure = $_.Exception.Message +} finally { + if ($browser) { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($server) { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 250 + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + link_worked = $linkWorked + overlay_worked = $overlayWorked + initial_index_hits = $initialIndexHits + initial_next_hits = $initialNextHits + after_link_next_hits = $afterLinkNextHits + after_overlay_index_hits = $afterOverlayIndexHits + error = $failure + browser_gone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + server_gone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + } | ConvertTo-Json -Depth 6 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/chrome-history-overlay-probe.ps1 b/tmp-browser-smoke/wrapped-link/chrome-history-overlay-probe.ps1 new file mode 100644 index 000000000..e56a31763 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/chrome-history-overlay-probe.ps1 @@ -0,0 +1,157 @@ +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\wrapped-link" +$port = 8148 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$beforePng = Join-Path $root "chrome-history-overlay.before.png" +$browserOut = Join-Path $root "chrome-history-overlay.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-history-overlay.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-history-overlay.server.stdout.txt" +$serverErr = Join-Path $root "chrome-history-overlay.server.stderr.txt" +Remove-Item $beforePng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Count-Hits([string]$Pattern) { + if (-not (Test-Path $serverErr)) { return 0 } + return ([regex]::Matches((Get-Content $serverErr -Raw), $Pattern)).Count +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$linkWorked = $false +$overlayWorked = $false +$initialIndexHits = 0 +$initialNextHits = 0 +$afterLinkNextHits = 0 +$afterOverlayIndexHits = 0 +$overlayPoint = $null +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "history overlay probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$beforePng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "history overlay probe screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "history overlay probe window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + + $initialIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + $initialNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + + $bmp = [System.Drawing.Bitmap]::new($beforePng) + try { + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $blue.min_x) { throw "history overlay probe could not find wrapped link" } + + $linkX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $linkY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + [void](Invoke-SmokeClientClick $hwnd $linkX $linkY) + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterLinkNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + if ($afterLinkNextHits -gt $initialNextHits) { + $linkWorked = $true + break + } + } + if (-not $linkWorked) { throw "history overlay probe did not reach next page before opening overlay" } + + Start-Sleep -Milliseconds 1200 + Show-SmokeWindow $hwnd + Send-SmokeCtrlH + Start-Sleep -Milliseconds 300 + + $historyRow0Y = 100 + 28 + 12 + $overlayPoint = Invoke-SmokeClientClick $hwnd 80 $historyRow0Y + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterOverlayIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + if ($afterOverlayIndexHits -gt $initialIndexHits) { + $overlayWorked = $true + break + } + } + if (-not $overlayWorked) { throw "history overlay probe did not traverse back to the selected history row" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + link_worked = $linkWorked + overlay_worked = $overlayWorked + initial_index_hits = $initialIndexHits + initial_next_hits = $initialNextHits + after_link_next_hits = $afterLinkNextHits + after_overlay_index_hits = $afterOverlayIndexHits + overlay_point = $overlayPoint + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/chrome-history-probe.ps1 b/tmp-browser-smoke/wrapped-link/chrome-history-probe.ps1 new file mode 100644 index 000000000..c1a6fa33b --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/chrome-history-probe.ps1 @@ -0,0 +1,160 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8147 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$beforePng = Join-Path $root "chrome-history.before.png" +$browserOut = Join-Path $root "chrome-history.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-history.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-history.server.stdout.txt" +$serverErr = Join-Path $root "chrome-history.server.stderr.txt" +Remove-Item $beforePng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +function Count-Hits([string]$Pattern) { + if (-not (Test-Path $serverErr)) { return 0 } + return ([regex]::Matches((Get-Content $serverErr -Raw), $Pattern)).Count +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$linkWorked = $false +$backWorked = $false +$forwardWorked = $false +$initialIndexHits = 0 +$initialNextHits = 0 +$afterLinkNextHits = 0 +$finalIndexHits = 0 +$finalNextHits = 0 +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "chrome history probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$beforePng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "chrome history screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "chrome history window handle not found" } + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + + $initialIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + $initialNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + + $bmp = [System.Drawing.Bitmap]::new($beforePng) + try { + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + $linkX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $linkY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + + [void](Invoke-SmokeClientClick $hwnd $linkX $linkY) + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $afterLinkNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + if ($afterLinkNextHits -gt $initialNextHits) { + $linkWorked = $true + break + } + } + if ($linkWorked) { Start-Sleep -Milliseconds 1500 } + + [void](Invoke-SmokeClientClick $hwnd 25 40) + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $finalIndexHits = Count-Hits 'GET /index\.html HTTP/1\.1" 200' + if ($finalIndexHits -gt $initialIndexHits) { + $backWorked = $true + break + } + } + if ($backWorked) { Start-Sleep -Milliseconds 1500 } + + [void](Invoke-SmokeClientClick $hwnd 57 40) + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $finalNextHits = Count-Hits 'GET /next\.html HTTP/1\.1" 200' + if ($finalNextHits -gt $afterLinkNextHits) { + $forwardWorked = $true + break + } + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + link_worked = $linkWorked + back_worked = $backWorked + forward_worked = $forwardWorked + initial_index_hits = $initialIndexHits + initial_next_hits = $initialNextHits + after_link_next_hits = $afterLinkNextHits + final_index_hits = $finalIndexHits + final_next_hits = $finalNextHits + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/chrome-reload-probe.ps1 b/tmp-browser-smoke/wrapped-link/chrome-reload-probe.ps1 new file mode 100644 index 000000000..6dc11944a --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/chrome-reload-probe.ps1 @@ -0,0 +1,100 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8146 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "chrome-reload.browser.stdout.txt" +$browserErr = Join-Path $root "chrome-reload.browser.stderr.txt" +$serverOut = Join-Path $root "chrome-reload.server.stdout.txt" +$serverErr = Join-Path $root "chrome-reload.server.stderr.txt" +$png = Join-Path $root "chrome-reload.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$png -Force -ErrorAction SilentlyContinue + +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Count-IndexHits { + if (-not (Test-Path $serverErr)) { return 0 } + return ([regex]::Matches((Get-Content $serverErr -Raw), 'GET /index\.html HTTP/1\.1" 200')).Count +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$reloadWorked = $false +$initialIndexHits = 0 +$finalIndexHits = 0 +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "chrome reload probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$png -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $png) -and ((Get-Item $png).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "chrome reload screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "chrome reload window handle not found" } + + $initialIndexHits = Count-IndexHits + + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + [void](Invoke-SmokeClientClick $hwnd 89 40) + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $finalIndexHits = Count-IndexHits + if ($finalIndexHits -gt $initialIndexHits) { + $reloadWorked = $true + break + } + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + initial_index_hits = $initialIndexHits + final_index_hits = $finalIndexHits + reload_worked = $reloadWorked + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/hover-probe.ps1 b/tmp-browser-smoke/wrapped-link/hover-probe.ps1 new file mode 100644 index 000000000..c527e172e --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/hover-probe.ps1 @@ -0,0 +1,112 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8148 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "hover.browser.stdout.txt" +$browserErr = Join-Path $root "hover.browser.stderr.txt" +$serverOut = Join-Path $root "hover.server.stdout.txt" +$serverErr = Join-Path $root "hover.server.stderr.txt" +$png = Join-Path $root "hover.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$png -Force -ErrorAction SilentlyContinue + +Add-Type @" +using System; +using System.Runtime.InteropServices; +public static class HoverProbeUser32 { + [StructLayout(LayoutKind.Sequential)] + public struct POINT { + public int X; + public int Y; + } + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + public static extern bool SetCursorPos(int X, int Y); +} +"@ + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$aliveAfterHover = $false +$failure = $null +$hoverClientX = 133 +$hoverClientY = 186 +$hoverPoint = New-Object HoverProbeUser32+POINT + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "hover probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$png -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $png) -and ((Get-Item $png).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "hover screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "hover window handle not found" } + + [void][HoverProbeUser32]::ShowWindow($hwnd, 5) + [void][HoverProbeUser32]::SetForegroundWindow($hwnd) + Start-Sleep -Milliseconds 250 + $hoverPoint.X = $hoverClientX + $hoverPoint.Y = $hoverClientY + [void][HoverProbeUser32]::ClientToScreen($hwnd, [ref]$hoverPoint) + [void][HoverProbeUser32]::SetCursorPos($hoverPoint.X, $hoverPoint.Y) + Start-Sleep -Milliseconds 1500 + $aliveAfterHover = [bool](Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + hover_client = [ordered]@{ x = $hoverClientX; y = $hoverClientY } + hover_screen = [ordered]@{ x = $hoverPoint.X; y = $hoverPoint.Y } + alive_after_hover = $aliveAfterHover + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/index.html b/tmp-browser-smoke/wrapped-link/index.html new file mode 100644 index 000000000..f5f41801f --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/index.html @@ -0,0 +1,18 @@ + + + + + Wrapped Link Smoke + + +
+

+ + FIRST + SECOND + +

+

Blue chip should be clickable after the wrap.

+
+ + diff --git a/tmp-browser-smoke/wrapped-link/link-down-probe.ps1 b/tmp-browser-smoke/wrapped-link/link-down-probe.ps1 new file mode 100644 index 000000000..2db0fb647 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/link-down-probe.ps1 @@ -0,0 +1,117 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8147 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$browserOut = Join-Path $root "linkdown.browser.stdout.txt" +$browserErr = Join-Path $root "linkdown.browser.stderr.txt" +$serverOut = Join-Path $root "linkdown.server.stdout.txt" +$serverErr = Join-Path $root "linkdown.server.stderr.txt" +$png = Join-Path $root "linkdown.before.png" +Remove-Item $browserOut,$browserErr,$serverOut,$serverErr,$png -Force -ErrorAction SilentlyContinue + +Add-Type @" +using System; +using System.Runtime.InteropServices; +public static class LinkDownUser32 { + [StructLayout(LayoutKind.Sequential)] + public struct POINT { + public int X; + public int Y; + } + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + public static extern bool SetCursorPos(int X, int Y); + + [DllImport("user32.dll")] + public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo); +} +"@ + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$aliveAfterDown = $false +$failure = $null +$clickClientX = 133 +$clickClientY = 186 +$clickPoint = New-Object LinkDownUser32+POINT + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "link-down probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$png -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $png) -and ((Get-Item $png).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "link-down screenshot did not become ready" } + + $hwnd = [IntPtr]::Zero + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "link-down window handle not found" } + + [void][LinkDownUser32]::ShowWindow($hwnd, 5) + [void][LinkDownUser32]::SetForegroundWindow($hwnd) + Start-Sleep -Milliseconds 250 + $clickPoint.X = $clickClientX + $clickPoint.Y = $clickClientY + [void][LinkDownUser32]::ClientToScreen($hwnd, [ref]$clickPoint) + [void][LinkDownUser32]::SetCursorPos($clickPoint.X, $clickPoint.Y) + Start-Sleep -Milliseconds 100 + [LinkDownUser32]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) + Start-Sleep -Milliseconds 1500 + $aliveAfterDown = [bool](Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + click_client = [ordered]@{ x = $clickClientX; y = $clickClientY } + click_screen = [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } + alive_after_down = $aliveAfterDown + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/wrapped-link/next.html b/tmp-browser-smoke/wrapped-link/next.html new file mode 100644 index 000000000..6dd60a924 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/next.html @@ -0,0 +1,13 @@ + + + + + Wrapped Link Target + + +
+

Navigation landed

+

Wrapped link navigation succeeded.

+
+ + diff --git a/tmp-browser-smoke/wrapped-link/probe.ps1 b/tmp-browser-smoke/wrapped-link/probe.ps1 new file mode 100644 index 000000000..d6bd8e2b3 --- /dev/null +++ b/tmp-browser-smoke/wrapped-link/probe.ps1 @@ -0,0 +1,151 @@ +$ErrorActionPreference = "Stop" +$root = "C:\Users\adyba\src\lightpanda-browser\tmp-browser-smoke\wrapped-link" +$port = 8142 +$browserExe = "C:\Users\adyba\src\lightpanda-browser\zig-out\bin\lightpanda.exe" +$beforePng = Join-Path $root "wrapped-before.png" +$browserOut = Join-Path $root "browser.stdout.txt" +$browserErr = Join-Path $root "browser.stderr.txt" +$serverOut = Join-Path $root "server.stdout.txt" +$serverErr = Join-Path $root "server.stderr.txt" +Remove-Item $beforePng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$red = $null +$blue = $null +$wrapped = $false +$clickClientX = $null +$clickClientY = $null +$clickPoint = $null +$hwnd = [IntPtr]::Zero +$titleBefore = $null +$titleAfter = $null +$navigated = $false +$serverSawNext = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "wrapped-link probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","240","--window_height","480","--screenshot_png",$beforePng -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "wrapped-link screenshot did not become ready" } + + $proc = $null + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "wrapped-link window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($beforePng) + try { + $red = Get-ColorBounds $bmp { param($c) $c.R -ge 170 -and $c.G -le 90 -and $c.B -le 90 } + $blue = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + + if ($null -ne $red.min_y -and $null -ne $blue.min_y) { + $wrapped = (($blue.min_y - $red.min_y) -ge 20) + } + if (-not $wrapped) { throw "wrapped-link fixture did not wrap as expected" } + + $clickClientX = [int][Math]::Floor(($blue.min_x + $blue.max_x) / 2) + $clickClientY = [int][Math]::Floor(($blue.min_y + $blue.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 250 + $titleBefore = Get-SmokeWindowTitle $hwnd + $clickPoint = Invoke-SmokeClientClick $hwnd $clickClientX $clickClientY + + $titleAfter = $titleBefore + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $titleAfter = Get-SmokeWindowTitle $hwnd + if ($titleAfter -like "Wrapped Link Target*") { + $navigated = $true + break + } + } + if (-not $navigated -and (Test-Path $serverErr)) { + $serverLog = Get-Content $serverErr -Raw + $serverSawNext = $serverLog -match 'GET /next\.html HTTP/1\.1" 200' + if ($serverSawNext) { + $navigated = $true + } + } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + screenshot_path = $beforePng + screenshot_length = if (Test-Path $beforePng) { (Get-Item $beforePng).Length } else { 0 } + red_bounds = $red + blue_bounds = $blue + wrapped = $wrapped + click_client = if ($null -ne $clickClientX) { [ordered]@{ x = $clickClientX; y = $clickClientY } } else { $null } + click_screen = if ($null -ne $clickClientX) { [ordered]@{ x = $clickPoint.X; y = $clickPoint.Y } } else { $null } + title_before = $titleBefore + title_after = $titleAfter + navigated = $navigated + server_saw_next = $serverSawNext + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/zoom/chrome-zoom-probe.ps1 b/tmp-browser-smoke/zoom/chrome-zoom-probe.ps1 new file mode 100644 index 000000000..24a3c3648 --- /dev/null +++ b/tmp-browser-smoke/zoom/chrome-zoom-probe.ps1 @@ -0,0 +1,150 @@ +$ErrorActionPreference = "Stop" +$repo = "C:\Users\adyba\src\lightpanda-browser" +$root = Join-Path $repo "tmp-browser-smoke\zoom" +$port = 8145 +$browserExe = Join-Path $repo "zig-out\bin\lightpanda.exe" +$beforePng = Join-Path $root "zoom-before.png" +$browserOut = Join-Path $root "zoom-browser.stdout.txt" +$browserErr = Join-Path $root "zoom-browser.stderr.txt" +$serverOut = Join-Path $root "zoom-server.stdout.txt" +$serverErr = Join-Path $root "zoom-server.stderr.txt" +Remove-Item $beforePng,$browserOut,$browserErr,$serverOut,$serverErr -Force -ErrorAction SilentlyContinue +Get-ChildItem -Path $repo -Filter "lightpanda-screenshot-*.png" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + +Add-Type -AssemblyName System.Drawing +. "$PSScriptRoot\..\common\Win32Input.ps1" + +function Get-ColorBounds([System.Drawing.Bitmap]$Bitmap, [scriptblock]$Matcher) { + $bounds = [ordered]@{min_x=$null; min_y=$null; max_x=$null; max_y=$null; count=0} + for ($y = 0; $y -lt $Bitmap.Height; $y++) { + for ($x = 0; $x -lt $Bitmap.Width; $x++) { + $c = $Bitmap.GetPixel($x, $y) + if (& $Matcher $c) { + if ($null -eq $bounds.min_x -or $x -lt $bounds.min_x) { $bounds.min_x = $x } + if ($null -eq $bounds.min_y -or $y -lt $bounds.min_y) { $bounds.min_y = $y } + if ($null -eq $bounds.max_x -or $x -gt $bounds.max_x) { $bounds.max_x = $x } + if ($null -eq $bounds.max_y -or $y -gt $bounds.max_y) { $bounds.max_y = $y } + $bounds.count++ + } + } + } + return $bounds +} + +$server = $null +$browser = $null +$ready = $false +$pngReady = $false +$hwnd = [IntPtr]::Zero +$beforeBounds = $null +$afterBounds = $null +$afterPng = $null +$wheelPoint = $null +$zoomWorked = $false +$failure = $null + +try { + $server = Start-Process -FilePath "python" -ArgumentList "-m","http.server",$port,"--bind","127.0.0.1" -WorkingDirectory $root -PassThru -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr + for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 250 + try { + $resp = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:$port/index.html" -TimeoutSec 2 + if ($resp.StatusCode -eq 200) { $ready = $true; break } + } catch {} + } + if (-not $ready) { throw "zoom probe server did not become ready" } + + $browser = Start-Process -FilePath $browserExe -ArgumentList "browse","http://127.0.0.1:$port/index.html","--window_width","320","--window_height","420","--screenshot_png",$beforePng -WorkingDirectory $repo -PassThru -RedirectStandardOutput $browserOut -RedirectStandardError $browserErr + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + if ((Test-Path $beforePng) -and ((Get-Item $beforePng).Length -gt 0)) { $pngReady = $true; break } + } + if (-not $pngReady) { throw "zoom probe screenshot did not become ready" } + + for ($i = 0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 250 + $proc = Get-Process -Id $browser.Id -ErrorAction SilentlyContinue + if ($proc -and $proc.MainWindowHandle -ne 0) { + $hwnd = [IntPtr]$proc.MainWindowHandle + break + } + } + if ($hwnd -eq [IntPtr]::Zero) { throw "zoom probe window handle not found" } + + $bmp = [System.Drawing.Bitmap]::new($beforePng) + try { + $beforeBounds = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $beforeBounds.min_x) { throw "zoom probe could not find initial blue box" } + + $clientX = [int][Math]::Floor(($beforeBounds.min_x + $beforeBounds.max_x) / 2) + $clientY = [int][Math]::Floor(($beforeBounds.min_y + $beforeBounds.max_y) / 2) + Show-SmokeWindow $hwnd + Start-Sleep -Milliseconds 300 + $wheelPoint = Invoke-SmokeClientCtrlWheel $hwnd $clientX $clientY 120 + Start-Sleep -Milliseconds 300 + Send-SmokeCtrlShiftP + + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 250 + $latest = Get-ChildItem -Path $repo -Filter "lightpanda-screenshot-*.png" -ErrorAction SilentlyContinue | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1 + if ($latest) { + $afterPng = $latest.FullName + break + } + } + if (-not $afterPng) { throw "zoom probe did not create after screenshot" } + + $bmp = [System.Drawing.Bitmap]::new($afterPng) + try { + $afterBounds = Get-ColorBounds $bmp { param($c) $c.B -ge 150 -and $c.R -le 90 -and $c.G -le 120 } + } finally { + $bmp.Dispose() + } + if ($null -eq $afterBounds.min_x) { throw "zoom probe could not find zoomed blue box" } + + $beforeWidth = $beforeBounds.max_x - $beforeBounds.min_x + 1 + $beforeHeight = $beforeBounds.max_y - $beforeBounds.min_y + 1 + $afterWidth = $afterBounds.max_x - $afterBounds.min_x + 1 + $afterHeight = $afterBounds.max_y - $afterBounds.min_y + 1 + $zoomWorked = ($afterWidth -gt $beforeWidth) -and ($afterHeight -gt $beforeHeight) + if (-not $zoomWorked) { throw "zoom probe box did not grow after Ctrl+wheel" } +} catch { + $failure = $_.Exception.Message +} finally { + $serverMeta = if ($server) { Get-CimInstance Win32_Process -Filter "ProcessId=$($server.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + $browserMeta = if ($browser) { Get-CimInstance Win32_Process -Filter "ProcessId=$($browser.Id)" | Select-Object Name,ProcessId,CommandLine,CreationDate } else { $null } + if ($browserMeta -and $browserMeta.CommandLine -and $browserMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $browser.Id -Force -ErrorAction SilentlyContinue } + if ($serverMeta -and $serverMeta.CommandLine -and $serverMeta.CommandLine -notmatch "codex\\.js|@openai/codex") { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 200 + $browserGone = if ($browser) { -not (Get-Process -Id $browser.Id -ErrorAction SilentlyContinue) } else { $true } + $serverGone = if ($server) { -not (Get-Process -Id $server.Id -ErrorAction SilentlyContinue) } else { $true } + + [ordered]@{ + server_pid = if ($server) { $server.Id } else { 0 } + browser_pid = if ($browser) { $browser.Id } else { 0 } + ready = $ready + screenshot_ready = $pngReady + before_png = $beforePng + after_png = $afterPng + before_bounds = $beforeBounds + after_bounds = $afterBounds + wheel_screen = if ($wheelPoint) { [ordered]@{ x = $wheelPoint.X; y = $wheelPoint.Y } } else { $null } + before_width = if ($beforeBounds -and $null -ne $beforeBounds.min_x) { $beforeBounds.max_x - $beforeBounds.min_x + 1 } else { 0 } + before_height = if ($beforeBounds -and $null -ne $beforeBounds.min_y) { $beforeBounds.max_y - $beforeBounds.min_y + 1 } else { 0 } + after_width = if ($afterBounds -and $null -ne $afterBounds.min_x) { $afterBounds.max_x - $afterBounds.min_x + 1 } else { 0 } + after_height = if ($afterBounds -and $null -ne $afterBounds.min_y) { $afterBounds.max_y - $afterBounds.min_y + 1 } else { 0 } + zoom_worked = $zoomWorked + error = $failure + server_meta = $serverMeta + browser_meta = $browserMeta + browser_gone = $browserGone + server_gone = $serverGone + } | ConvertTo-Json -Depth 7 +} + +if ($failure) { + exit 1 +} diff --git a/tmp-browser-smoke/zoom/index.html b/tmp-browser-smoke/zoom/index.html new file mode 100644 index 000000000..e59478127 --- /dev/null +++ b/tmp-browser-smoke/zoom/index.html @@ -0,0 +1,26 @@ + + + + + Zoom Smoke + + + +

Zoom Smoke

+

Ctrl+wheel should enlarge the image below.

+ zoom target + +