module BiDi.Demos.BrowsingContextDemos where import BiDi.Actions (BiDiActions (..)) import BiDi.DemoUtils import Const (milliseconds) import Data.Aeson (Value (Null), object, (.=)) import Data.Text (Text) import IOUtils (DemoActions (..)) import TestData (contentPageUrl, framesUrl, loginUrl, navigation1Url, navigation2Url, navigation3Url, navigation4Url, navigation5Url, navigation6Url, nestedFramesUrl, scriptRealmUrl) import WebDriverPreCore.BiDi.Protocol ( Activate (..), CaptureScreenshot (..), ClipRectangle (..), Close (..), ContextTarget (..), Create (..), CreateType (..), CreateUserContext (..), Evaluate (..), GetTree (..), GetTreeResult (..), HandleUserPrompt (..), ImageFormat (..), Info (..), JSInt (..), JSUInt (..), LocateNodes (..), LocateNodesResult (..), Locator (..), Navigate (..), NodeRemoteValue (..), Orientation (..), PageRange (..), Print (..), PrintMargin (..), PrintPage (..), PrintResult (..), ReadinessState (..), Reload (..), ScreenShotOrigin (..), SetViewport (..), SharedId (..), SharedReference (..), Target (..), TraverseHistory (..), Viewport (..), ) import Prelude hiding (log, putStrLn) -- >>> runDemo browsingContextCreateActivateCloseDemo browsingContextCreateActivateCloseDemo :: BiDiDemo browsingContextCreateActivateCloseDemo = demo "Browsing Context - Create, Activate, Close" action where action :: DemoActions -> BiDiActions -> IO () action MkDemoActions {..} MkBiDiActions {..} = do logTxt "New browsing context - Tab" let bcParams = MkCreate { createType = Tab, background = False, referenceContext = Nothing, userContext = Nothing } bc <- browsingContextCreate bcParams logShow "Browsing context - Tab" bc pause logTxt "New browsing context - Window" bcWin <- browsingContextCreate bcParams {createType = Window} logShow "Browsing context - Window" bcWin pause logTxt "New browsing context - Tab with reference context" bcWithContext <- browsingContextCreate bcParams {referenceContext = Just bc} logShow "Browsing context - Tab with reference context" bcWithContext pause logTxt "New browsing context - Background" bg <- browsingContextCreate bcParams { background = True, referenceContext = Just bcWin } logShow "Background browsing context created on front window" bg pause logTxt "New user context" uc <- browserCreateUserContext MkCreateUserContext { insecureCerts = Nothing, proxy = Nothing, unhandledPromptBehavior = Nothing } logShow "User context created" uc pause logTxt "New browsing context - Window with user context" bcWinWithUC <- browsingContextCreate bcParams { createType = Window, userContext = Just uc } logShow "Browsing context - Window with user context" bcWinWithUC pause logTxt "Activate initial browsing context" o <- browsingContextActivate $ MkActivate bc logShow "Activate result" o pause logTxt "Close initial browsing context" co <- browsingContextClose $ MkClose { context = bc, promptUnload = Nothing } logShow "Close result" co pause -- >>> runDemo browsingContextCaptureScreenshotCloseDemo browsingContextCaptureScreenshotCloseDemo :: BiDiDemo browsingContextCaptureScreenshotCloseDemo = demo "Browsing Context - Capture Screenshot, Close" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi logTxt "Capture screenshot - default" screenshot <- browsingContextCaptureScreenshot $ MkCaptureScreenshot bc Nothing Nothing Nothing logShow "Screenshot captured" screenshot pause logTxt "Capture screenshot - document, format png" screenshotDoc <- browsingContextCaptureScreenshot $ MkCaptureScreenshot { context = bc, origin = (Just Document), format = Just $ MkImageFormat { imageType = "image/png", quality = Just 0.75 }, clip = Nothing } logShow "Screenshot captured" screenshotDoc pause logTxt "Capture screenshot - viewport, clip" screenshotViewport <- browsingContextCaptureScreenshot $ MkCaptureScreenshot { context = bc, origin = (Just Viewport), format = Nothing, clip = Just $ BoxClipRectangle {x = 0, y = 0, width = 300, height = 300} } logShow "Screenshot captured" screenshotViewport pause logTxt "Close browsing context - unload prompt False" co <- browsingContextClose $ MkClose {context = bc, promptUnload = Just False} logShow "Close result" co pause -- >>> runDemo browsingContextClosePromptUnloadDemo browsingContextClosePromptUnloadDemo :: BiDiDemo browsingContextClosePromptUnloadDemo = demo "Browsing Context - Close with Unload Prompt" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi -- TODO :: promptUnload doesn't seem to do anything ?? logTxt "Close browsing context - unload prompt True" co <- browsingContextClose $ MkClose {context = bc, promptUnload = Just True} logShow "Close result" co pause -- >>> runDemo browsingContextGetTreeDemo browsingContextGetTreeDemo :: BiDiDemo browsingContextGetTreeDemo = demo "Browsing Context - Get Tree" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do logTxt "Get browsing context tree - all" tree <- browsingContextGetTree $ MkGetTree Nothing Nothing logShow "Browsing context tree" tree pause logTxt "Create opener browsing context (Window)" openerContext <- newWindowContext utils bidi logTxt "Create opened browsing context (Tab with opener reference)" openedContext <- browsingContextCreate $ MkCreate { createType = Tab, background = False, referenceContext = Just openerContext, userContext = Nothing } logShow "Opened browsing context" openedContext pause logTxt "Get browsing context tree - WebDriver created contexts (originalOpener likely null)" treeWithOpener <- browsingContextGetTree MkGetTree { maxDepth = Nothing, root = Nothing -- Get all contexts to see the relationship } logShow "Browsing context tree - WebDriver created (originalOpener should be null)" treeWithOpener pause -- TODO: when using Scripts - get browsingContextGetTree with originalOpener logTxt "NOTE: originalOpener is null because WebDriver.create doesn't set opener relationships" logTxt "originalOpener is only set when web content opens windows via window.open(), not WebDriver commands" pause logTxt "IMPORTANT: referenceContext does NOT create parent-child relationships!" logTxt "Parent-child relationships only exist between main documents and their iframes" logTxt "All WebDriver-created tabs/windows are top-level contexts with no parent" pause logTxt "Demonstrating: All created contexts are top-level (no parent, no children)" logTxt "Get tree from 'opener' context - will show NO children (not a parent)" treeFromOpener <- browsingContextGetTree MkGetTree { maxDepth = Nothing, root = Just openerContext } logShow "Tree from opener context (no children expected)" treeFromOpener pause logTxt "Get tree from 'opened' context - will show NO parent (top-level)" treeFromOpened <- browsingContextGetTree MkGetTree { maxDepth = Nothing, root = Just openedContext } logShow "Tree from opened context (no parent expected)" treeFromOpened pause logTxt "Get full tree - shows all top-level contexts as siblings" fullTree <- browsingContextGetTree MkGetTree { maxDepth = Nothing, root = Nothing } logShow "Full tree (all contexts are top-level siblings)" fullTree pause -- TODO: when using IFrames - get parent-child relationships logTxt "To see parent-child relationships, you need:" logTxt "1. Navigate to a page with iframes" logTxt "2. The main document = parent, iframes = children" logTxt "3. referenceContext is only for tab positioning, not hierarchy" pause -- >>> runDemo browsingContextHandleUserPromptDemo browsingContextHandleUserPromptDemo :: BiDiDemo browsingContextHandleUserPromptDemo = demo "Browsing Context - Handle User Prompt" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi logTxt "Test 1: Create and handle an alert dialog" scriptEvaluateNoWait $ MkEvaluate { expression = "alert('Hello from Pyrethrum BiDi!')", target = ContextTarget $ MkContextTarget { context = bc, sandbox = Nothing }, awaitPromise = False, resultOwnership = Nothing, serializationOptions = Nothing } -- Wait for the alert to be displayed -- in a production automation more sophisticated polling or event subscription would be used pauseAtLeast $ 500 * milliseconds logTxt "Accept the alert dialog" acceptResult <- browsingContextHandleUserPrompt $ MkHandleUserPrompt { context = bc, accept = Just True, userText = Nothing } logShow "Alert accept result" acceptResult pause logTxt "Test 2: Create and handle a confirm dialog" scriptEvaluateNoWait $ MkEvaluate { expression = "confirm('Hello from Pyrethrum BiDi. Do you want to continue?')", target = ContextTarget $ MkContextTarget {context = bc, sandbox = Nothing}, awaitPromise = False, resultOwnership = Nothing, serializationOptions = Nothing } -- Wait for the alert to be displayed (in a production automation more sophisticated polling must be used) pauseAtLeast $ 500 * milliseconds logTxt "Dismiss the confirm dialog" dismissResult <- browsingContextHandleUserPrompt $ MkHandleUserPrompt { context = bc, accept = Just False, userText = Nothing } logShow "Confirm dismiss result" dismissResult pause logTxt "Test 3: Create and handle a prompt dialog with user input" scriptEvaluateNoWait $ MkEvaluate { expression = "prompt('What is your name?', 'Default Name')", target = ContextTarget $ MkContextTarget {context = bc, sandbox = Nothing}, awaitPromise = False, resultOwnership = Nothing, serializationOptions = Nothing } -- Wait for the alert to be displayed (in a productions system, more sophisticated polling would be needed) pauseAtLeast $ 500 * milliseconds logTxt "Accept prompt with custom text" promptResult <- browsingContextHandleUserPrompt $ MkHandleUserPrompt { context = bc, accept = Just True, userText = Just "John Doe from BiDi" } logShow "Prompt accept with text result" promptResult pause logTxt "Test 4: Create and dismiss a prompt dialog" scriptEvaluateNoWait $ MkEvaluate { expression = "prompt('Enter your age:')", target = ContextTarget $ MkContextTarget {context = bc, sandbox = Nothing}, awaitPromise = False, resultOwnership = Nothing, serializationOptions = Nothing } -- Wait for the alert to be displayed (in a productions system, more sophisticated polling would be needed) pauseAtLeast $ 500 * milliseconds logTxt "Dismiss the prompt dialog" dismissPromptResult <- browsingContextHandleUserPrompt $ MkHandleUserPrompt { context = bc, accept = Just False, userText = Nothing } logShow "Prompt dismiss result" dismissPromptResult pause -- >>> runDemo browsingNavigateReloadTraverseHistoryDemo browsingNavigateReloadTraverseHistoryDemo :: BiDiDemo browsingNavigateReloadTraverseHistoryDemo = demo "Browsing Context - Navigate, Reload, Traverse History" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi nav1 <- navigation1Url logTxt "Navigate to Navigation 1" navResult1 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav1, wait = Just Complete} logShow "Navigation result - Navigation 1" navResult1 pause nav2 <- navigation2Url logTxt "Navigate to Navigation 2" navResult2 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav2, wait = Just Interactive} logShow "Navigation result - Navigation 2" navResult2 pause nav3 <- navigation3Url logTxt "Navigate to Navigation 3" navResult3 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav3, wait = Just Complete} logShow "Navigation result - Navigation 3" navResult3 pause nav4 <- navigation4Url logTxt "Navigate to Navigation 4" navResult4 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav4, wait = Just Complete} logShow "Navigation result - Navigation 4" navResult4 pause logTxt "Reload current page (Navigation 4) - default options" reloadResult1 <- browsingContextReload $ MkReload {context = bc, ignoreCache = Nothing, wait = Just Complete} logShow "Reload result - default" reloadResult1 pause nav5 <- navigation5Url logTxt "Navigate to Navigation 5" navResult5 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav5, wait = Just Complete} logShow "Navigation result - Navigation 5" navResult5 pause -- ignore cache not supported yet in geckodriver -- logTxt "Reload current page (Navigation 5) - ignore cache" -- reloadResult2 <- browsingContextReload $ MkReload {context = bc, ignoreCache = Just True, wait = Nothing} -- logShow "Reload result - ignore cache" reloadResult2 -- pause nav6 <- navigation6Url logTxt "Navigate to Navigation 6" navResult6 <- browsingContextNavigate $ MkNavigate {context = bc, url = nav6, wait = Just Complete} logShow "Navigation result - Navigation 6" navResult6 pause logTxt "Reload current page (Navigation 6) - wait for complete" reloadResult3 <- browsingContextReload $ MkReload {context = bc, ignoreCache = Nothing, wait = Just Complete} logShow "Reload result - wait complete" reloadResult3 pause logTxt "Test history traversal - Go back 1 step (to Navigation 5)" historyResult1 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt (-1)} logShow "History traversal result - back 1" historyResult1 -- there is an issue in chromedriver where navigating back too quickly causes a failure -- would need a better solution in production code (retries, waits, event listening, etc ) pauseAtLeast (100 * milliseconds) logTxt "Go back 2 more steps (to Navigation 3)" historyResult2 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt (-2)} logShow "History traversal result - back 2" historyResult2 pauseAtLeast (100 * milliseconds) logTxt "Go back 1 more step (to Navigation 2)" historyResult3 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt (-1)} logShow "History traversal result - back 1" historyResult3 pauseAtLeast (100 * milliseconds) logTxt "Go forward 1 step (to Navigation 3)" historyResult4 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt 1} logShow "History traversal result - forward 1" historyResult4 pauseAtLeast (100 * milliseconds) logTxt "Go forward 2 steps (to Navigation 5)" historyResult5 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt 2} logShow "History traversal result - forward 2" historyResult5 pauseAtLeast (100 * milliseconds) logTxt "Go forward 1 step (to Navigation 6)" historyResult6 <- browsingContextTraverseHistory $ MkTraverseHistory {context = bc, delta = MkJSInt 1} logShow "History traversal result - forward 1" historyResult6 pauseAtLeast (100 * milliseconds) logTxt "Final navigation - back to Navigation 1" navResultFinal <- browsingContextNavigate $ MkNavigate {context = bc, url = nav1, wait = Just Complete} logShow "Navigation result - back to Navigation 1" navResultFinal pause -- >>> runDemo browsingContextLocateNodesDemo browsingContextLocateNodesDemo :: BiDiDemo browsingContextLocateNodesDemo = demo "Browsing Context - Locate Nodes with All Selectors and Options" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi loginPage <- loginUrl logTxt "Navigate to Login Page for comprehensive selector testing" navResult1 <- browsingContextNavigate $ MkNavigate {context = bc, url = loginPage, wait = Just Complete} logShow "Navigation result - Login page" navResult1 pause -- Test CSS Selector logTxt "Test 1: CSS Selector - Find username input field" cssResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "#username"}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "CSS Selector result - #username" cssResult pause logTxt "Test 2: CSS Selector - Find all input elements" cssAllInputs <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "input"}, maxNodeCount = Just (MkJSUInt 5), serializationOptions = Nothing, startNodes = Nothing } logShow "CSS Selector result - all inputs (max 5)" cssAllInputs pause -- Test XPath Selector logTxt "Test 3: XPath Selector - Find password field by attribute" xpathResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = XPath {value = "//input[@name='password']"}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "XPath Selector result - password field" xpathResult pause logTxt "Test 4: XPath Selector - Find login button by text content" xpathButtonResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = XPath {value = "//button[contains(text(), 'Login')]"}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "XPath Selector result - login button" xpathButtonResult pause -- Test SerializationOptions with different depths logTxt "Test 7: CSS Selector with SerializationOptions - Deep DOM traversal" cssWithSerializationResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "form"}, maxNodeCount = Nothing, serializationOptions = Just $ object [ "maxDomDepth" .= (3 :: Int), "maxObjectDepth" .= Null, "includeShadowTree" .= ("none" :: Text) ], startNodes = Nothing } logShow "CSS Selector with SerializationOptions - form with depth 3" cssWithSerializationResult pause logTxt "Test 8: CSS Selector with different SerializationOptions - Shallow traversal" cssShallowResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "body"}, maxNodeCount = Nothing, serializationOptions = Just $ object [ "maxDomDepth" .= (1 :: Int), "maxObjectDepth" .= (1 :: Int), "includeShadowTree" .= ("open" :: Text) ], startNodes = Nothing } logShow "CSS Selector with SerializationOptions - body with depth 1" cssShallowResult pause -- Navigate to a page with more accessibility content for better testing framesPage <- framesUrl logTxt "Navigate to Frames page for accessibility and context testing" navResult2 <- browsingContextNavigate $ MkNavigate {context = bc, url = framesPage, wait = Just Complete} logShow "Navigation result - Frames page" navResult2 pause -- Test Accessibility Selector logTxt "Test 9: Accessibility Selector - Find elements by role" accessibilityRoleResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = Accessibility { name = Nothing, role = Just "link" }, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "Accessibility Selector result - role 'link'" accessibilityRoleResult pause logTxt "Test 10: Accessibility Selector - Find elements by accessible name" accessibilityNameResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = Accessibility { name = Just "iFrame", role = Nothing }, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "Accessibility Selector result - name 'iFrame'" accessibilityNameResult pause logTxt "Test 11: Accessibility Selector - Find elements by both name and role" accessibilityBothResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = Accessibility { name = Just "Nested Frames", role = Just "link" }, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "Accessibility Selector result - name 'Nested Frames' and role 'link'" accessibilityBothResult pause -- Test startNodes functionality logTxt "Test 12: Demonstrating startNodes - First find a parent element, then search within it" parentResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "div.example"}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "Parent element search - div.example" parentResult pause -- Extract sharedId from first result to use as startNode case parentResult of MkLocateNodesResult nodes -> case nodes of (MkNodeRemoteValue {sharedId = Just (MkSharedId nodeId)} : _) -> do logTxt "Test 13: Using startNodes - Search for links within the parent element" startNodesResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = CSS {value = "a"}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Just [MkSharedReference {sharedId = MkSharedId {id = nodeId}, handle = Nothing, extensions = Nothing}] } logShow "StartNodes result - links within div.example" startNodesResult pause logTxt "Test 14: Using startNodes with different selector - Find all text content" startNodesTextResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = XPath {value = ".//text()[normalize-space()]"}, maxNodeCount = Just (MkJSUInt 5), serializationOptions = Just $ object [ "maxDomDepth" .= (2 :: Int), "maxObjectDepth" .= Null, "includeShadowTree" .= ("none" :: Text) ], startNodes = Just [MkSharedReference {sharedId = MkSharedId {id = nodeId}, handle = Nothing, extensions = Nothing}] } logShow "StartNodes with XPath - text nodes within parent (max 5)" startNodesTextResult pause _ -> do logTxt "No sharedId found in first result, skipping startNodes test" pause -- Navigate to a simpler page for Context selector test nestedFrames <- nestedFramesUrl logTxt "Navigate to Nested Frames for Context selector testing" navResult3 <- browsingContextNavigate $ MkNavigate {context = bc, url = nestedFrames, wait = Just Complete} logShow "Navigation result - Nested Frames page" navResult3 pause -- Get child browsing contexts (frames) for Context selector logTxt "Get browsing context tree to find child frames" treeResult <- browsingContextGetTree $ MkGetTree Nothing Nothing logShow "Browsing context tree" treeResult pause -- >>> runDemo browsingContextContextLocatorDemo browsingContextContextLocatorDemo :: BiDiDemo browsingContextContextLocatorDemo = demo "Browsing Context - Context Locator" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi scriptRealmPage <- scriptRealmUrl logTxt "Navigate to Script Realm page with iframe" _ <- browsingContextNavigate $ MkNavigate {context = bc, url = scriptRealmPage, wait = Just Complete} pause -- Click the button to create an iframe using script.evaluate logTxt "Click button to create iframe" _ <- scriptEvaluate $ MkEvaluate { expression = "document.getElementById('createIframe').click()", target = ContextTarget $ MkContextTarget {context = bc, sandbox = Nothing}, awaitPromise = False, resultOwnership = Nothing, serializationOptions = Nothing } pauseAtLeast (500 * milliseconds) -- Get the tree to find the iframe's browsing context logTxt "Get tree to find iframe browsing context" MkGetTreeResult {contexts} <- browsingContextGetTree $ MkGetTree {maxDepth = Nothing, root = Just bc} logShow "Tree result" contexts case contexts of (MkInfo {children = Just (iframeInfo : _)} : _) -> do let iframeContext = iframeInfo.context logShow "Found iframe context" iframeContext -- Use Context locator to locate nodes within the iframe logTxt "Using Context locator to find elements in iframe" contextResult <- browsingContextLocateNodes $ MkLocateNodes { context = bc, locator = Context {context = iframeContext}, maxNodeCount = Nothing, serializationOptions = Nothing, startNodes = Nothing } logShow "Context locator result" contextResult pause _ -> logTxt "No iframe child context found" -- >>> runDemo browsingContextPrintDemo browsingContextPrintDemo :: BiDiDemo browsingContextPrintDemo = demo "Browsing Context - Print" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi contentPage <- contentPageUrl logTxt "Navigate to Content Page for print testing" navResult <- browsingContextNavigate $ MkNavigate {context = bc, url = contentPage, wait = Just Complete} logShow "Navigation result" navResult pause logTxt "Test 1: Print - Default settings" printDefault <- browsingContextPrint $ MkPrint {context = bc, background = Nothing, margin = Nothing, orientation = Nothing, page = Nothing, pageRanges = Nothing, scale = Nothing, shrinkToFit = Nothing} logTxt $ "Print result - default settings: " <> printDefault.base64Text pause logTxt "Test 2: Print - Portrait orientation with custom margins" printPortraitMargins <- browsingContextPrint $ MkPrint { context = bc, background = Just False, margin = Just $ MkPrintMargin { bottom = Just 2.0, left = Just 1.5, right = Just 1.5, top = Just 2.0 }, orientation = Just Portrait, page = Nothing, pageRanges = Nothing, scale = Nothing, shrinkToFit = Nothing } logTxt $ "Print result - portrait with margins: " <> printPortraitMargins.base64Text pause logTxt "Test 3: Print - Landscape orientation with custom page size" printLandscapePage <- browsingContextPrint $ MkPrint { context = bc, background = Just True, margin = Nothing, orientation = Just Landscape, page = Just $ MkPrintPage { height = Just 29.7, -- A4 height in cm width = Just 21.0 -- A4 width in cm }, pageRanges = Nothing, scale = Nothing, shrinkToFit = Nothing } logTxt $ "Print result - landscape with page size: " <> printLandscapePage.base64Text pause logTxt "Test 4: Print - Custom scale and shrink to fit" printScaled <- browsingContextPrint $ MkPrint { context = bc, background = Nothing, margin = Nothing, orientation = Nothing, page = Nothing, pageRanges = Nothing, scale = Just 0.8, shrinkToFit = Just True } logTxt $ "Print result - scaled 80%: " <> printScaled.base64Text pause logTxt "Test 5: Print with pageRanges - Only first page" printFirstPage <- browsingContextPrint $ MkPrint { context = bc, background = Nothing, margin = Nothing, orientation = Nothing, page = Nothing, pageRanges = Just [Page 1], scale = Nothing, shrinkToFit = Nothing } logTxt $ "Print result - first page only: " <> printFirstPage.base64Text pause logTxt "Test 6: Print with pageRanges - Pages 1-2" printPageRange <- browsingContextPrint $ MkPrint { context = bc, background = Nothing, margin = Nothing, orientation = Nothing, page = Nothing, pageRanges = Just $ [Range {fromPage = 1, toPage = 2}], scale = Nothing, shrinkToFit = Nothing } logTxt $ "Print result - pages 1-2: " <> printPageRange.base64Text pause logTxt "Test 7: Print with pageRanges - Specific pages (1,3)" printSpecificPages <- browsingContextPrint $ MkPrint { context = bc, background = Nothing, margin = Nothing, orientation = Nothing, page = Nothing, pageRanges = Just [Range 1 2, Page 3], scale = Nothing, shrinkToFit = Nothing } logTxt $ "Print result - pages 1 and 3: " <> printSpecificPages.base64Text pause -- >>> runDemo browsingContextSetViewportDemo browsingContextSetViewportDemo :: BiDiDemo browsingContextSetViewportDemo = demo "Browsing Context - Set Viewport" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi contentPage <- contentPageUrl logTxt "Navigate to Content Page for viewport testing" navResult <- browsingContextNavigate $ MkNavigate {context = bc, url = contentPage, wait = Just Complete} logShow "Navigation result" navResult pause logTxt "Test 1: Set Viewport - Standard desktop size" setViewportDesktop <- browsingContextSetViewport $ MkSetViewport { context = Just bc, viewport = Just $ Just $ MkViewport { width = MkJSUInt 1920, height = MkJSUInt 1080 }, devicePixelRatio = Nothing, userContexts = Nothing } logShow "Set viewport result - desktop" setViewportDesktop pause logTxt "Test 2: Set Viewport - Mobile size with device pixel ratio" setViewportMobile <- browsingContextSetViewport $ MkSetViewport { context = Just bc, viewport = Just $ Just $ MkViewport { width = MkJSUInt 375, height = MkJSUInt 812 }, devicePixelRatio = Just (Just 2.0), userContexts = Nothing } logShow "Set viewport result - mobile" setViewportMobile pause logTxt "Test 3: Set Viewport - Tablet size" setViewportTablet <- browsingContextSetViewport $ MkSetViewport { context = Just bc, viewport = Just $ Just $ MkViewport { width = MkJSUInt 768, height = MkJSUInt 1024 }, devicePixelRatio = Nothing, userContexts = Nothing } logShow "Set viewport result - tablet" setViewportTablet pause logTxt "Test 4: Print after viewport changes - Should reflect new dimensions" printAfterViewport <- browsingContextPrint $ MkPrint { context = bc, background = Nothing, margin = Nothing, orientation = Just Portrait, page = Nothing, pageRanges = Nothing, scale = Just 1.0, shrinkToFit = Just False } logTxt $ "Print result - after viewport changes: " <> printAfterViewport.base64Text pause -- >>> runDemo browsingContextSetViewportResetDemo browsingContextSetViewportResetDemo :: BiDiDemo browsingContextSetViewportResetDemo = demo "Browsing Context - Set Viewport Reset to Null" action where action :: DemoActions -> BiDiActions -> IO () action utils@MkDemoActions {..} bidi@MkBiDiActions {..} = do bc <- rootContext utils bidi contentPage <- contentPageUrl logTxt "Navigate to Content Page for viewport reset testing" navResult <- browsingContextNavigate $ MkNavigate {context = bc, url = contentPage, wait = Just Complete} logShow "Navigation result" navResult pause logTxt "Test 1: Set Viewport - Custom size first" setViewportCustom <- browsingContextSetViewport $ MkSetViewport { context = Just bc, viewport = Just $ Just $ MkViewport { width = MkJSUInt 800, height = MkJSUInt 600 }, devicePixelRatio = Just (Just 1.5), userContexts = Nothing } logShow "Set viewport result - custom size" setViewportCustom pause logTxt "Test 1: Set Viewport - Reset to null (default)" setViewportNull <- browsingContextSetViewport $ MkSetViewport { context = Just bc, viewport = Just Nothing, -- Explicitly set to null devicePixelRatio = Just Nothing, -- Reset device pixel ratio too userContexts = Nothing } logShow "Set viewport result - reset to null" setViewportNull pause