Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only one ChromoteSession allowed. #204

Open
chlebowa opened this issue Mar 6, 2025 · 8 comments
Open

Only one ChromoteSession allowed. #204

chlebowa opened this issue Mar 6, 2025 · 8 comments

Comments

@chlebowa
Copy link

chlebowa commented Mar 6, 2025

I am running a set of shinytest2-based tests in which multiple AppDriver objects are instantiated and later stopped with AppDirver$stop().

Within the last month these tests started failing and I was able to track the failure down to this:

> chromote::ChromoteSession$new()
<ChromoteSession> (session + target active)   
  Session ID: 80464A3006920168D7165E71AAC2CA5B
  Target ID:  DEF47C6FA8C52772C2B955261E0061F7
  Parent PID: 7404
> chromote::ChromoteSession$new()
Error in callback(...) : code: -32602
  message: Target position can only be set for new windows
>

This happens with

---- {chromote} ----
   System: x86_64-w64-mingw32
R version: R version 4.4.2 (2024-10-31 ucrt)
 chromote: 0.4.0

---- Chrome ----
   Path: C:\Program Files\Google\Chrome\Application\chrome.exe
Version: 134.0.6998.36
   Args: --headless --disable-gpu --force-color-profile=srgb  
         --disable-extensions --mute-audio
> 

as well as with

---- {chromote} ----
   System: x86_64-pc-linux-gnu
R version: R version 4.2.1 (2022-06-23)
 chromote: 0.4.0

---- Chrome ----
   Path: /usr/bin/google-chrome
Version: Google Chrome 134.0.6998.35
   Args: --headless --no-sandbox --disable-dev-shm-usage
         --force-color-profile=srgb --disable-extensions --mute-audio

and with chromote v0.3.1.

@gadenbuie
Copy link
Member

gadenbuie commented Mar 6, 2025

Thanks for opening this @chlebowa. I'm realizing now that this is likely a variant of (or at least related to) a similar issue I solved in rstudio/webshot2#72, but I now have more context.

In short, the change is part of the shifting Chrome DevTools Protocol after changes to the headless mode. The error seen above is thrown from

if (is.null(targetId)) {
p <- parent$Target$createTarget(
"about:blank",
width = width,
height = height,
wait_ = FALSE

and it appeared with v134 of Chrome, specifically the Chrome binary and not chrome-headless-shell

library(chromote) # pak::pak("rstudio/chromote")

with_chrome_version(133, {
  b <- ChromoteSession$new()
  c <- ChromoteSession$new()
})

with_chrome_version(134, {
  b <- ChromoteSession$new()
  c <- ChromoteSession$new()
})
#> Error in callback(...): code: -32602
#>   message: Target position can only be set for new windows

with_chrome_version(134, binary = "chrome-headless-shell", {
  b <- ChromoteSession$new()
  c <- ChromoteSession$new()
})

The Target.createTarget docs provide some hints:

width: Frame width in DIP (requires newWindow to be true or headless shell).

height: Frame height in DIP (requires newWindow to be true or headless shell).

newWindow: Whether to create a new Window or Tab (false by default, not supported by headless shell).

In particular, either we need to set newWindow = TRUE or be using headless shell for width and height to have any effect.

library(chromote) # pak::pak("rstudio/chromote")

show_browser_size <- function(b) {
  name <- deparse(substitute(b))
  windowId <- b$Browser$getWindowForTarget(b$get_target_id())$windowId
  bounds <- b$Browser$getWindowBounds(windowId)$bounds
  message(sprintf("`%s` has window dimensions: %s x %s", name, bounds$width, bounds$height))
}

with_chrome_version(133, {
  b <- ChromoteSession$new()
  show_browser_size(b)
  c <- ChromoteSession$new(width = 450, height = 900)
  show_browser_size(b)
  show_browser_size(c)
})
#> `b` has window dimensions: 1200 x 1371
#> `b` has window dimensions: 1200 x 1371
#> `c` has window dimensions: 1200 x 1371

with_chrome_version(134, binary = "chrome-headless-shell", {
  b <- ChromoteSession$new()
  show_browser_size(b)
  c <- ChromoteSession$new(width = 450, height = 900)
  show_browser_size(b)
  show_browser_size(c)
})
#> `b` has window dimensions: 992 x 1323
#> `b` has window dimensions: 992 x 1323
#> `c` has window dimensions: 450 x 900

This explains the change in behavior that happened in webshot2.

Here are a few options:

  1. Modify the existing Target$createTarget() call to set newWindow = TRUE, which I believe will generally keep chromote consistent between Chrome versions. There might be performance implications of opening a new window rather than opening a new tab in an existing window.

    With some testing, I've found that from Chrome 128-133 (inclusive), width and height have no effect regardless of the value of newWindow.

  2. Update ChromoteSession$new() to set the browser window size to width and height via Browser$setWindowBounds() and also (maybe) use newWindow = TRUE.

    Calling Browser$setWindowBounds() resizes the browser window, and this works reliably for Chrome after 128 as well as chrome-headless-shell. The downside is that when newWindow = FALSE, which is Chrome's default, this call will affect the size of any ChromoteSession attached to that window.

    # with newWindow = TRUE
    with_chrome_version(134, {
      b <- ChromoteSession$new()
      show_browser_size(b)
      c <- ChromoteSession$new(width = 450, height = 900)
      show_browser_size(b)
      show_browser_size(c)
    })
    #> `b` has window dimensions: 992 x 1323
    #> `b` has window dimensions: 992 x 1323
    #> `c` has window dimensions: 500 x 900
    
    # with newWindow in second call
    with_chrome_version(134, {
      b <- ChromoteSession$new()
      show_browser_size(b)
      c <- ChromoteSession$new(width = 450, height = 900, new_window = FALSE)
      show_browser_size(b)
      show_browser_size(c)
    })
    #> `b` has window dimensions: 992 x 1323
    #> `b` has window dimensions: 500 x 900
    #> `c` has window dimensions: 500 x 900

    Note that setting newWindow = FALSE throws for 128+ if a window isn't already open, but maybe we can work around this for the first call.

    with_chrome_version(128, {
      b <- ChromoteSession$new(new_window = FALSE)
      show_browser_size(b)
      c <- ChromoteSession$new(width = 450, height = 900)
      show_browser_size(b)
      show_browser_size(c)
    })
    #> Error in callback(...): code: -32000
    #> message: Failed to open new tab - no browser is open
  3. Another option is to use Emulation$setDeviceOverrideMetrics() as I did in fix: Set device width and height before screenshot webshot2#72. This approach appears to work everywhere, regardless of the value of newWindow, but has one complication: it requires setting the mobile flag and you can't submit the command without it. I think we can get around this by simply adding mobile to ChromoteSession$new().

    The advantage of this approach is that it's tab-specific, doesn't change the window for other sessions, and sets the inner width and height to exactly the requested dimensions.

    with_chrome_version(128, {
      message("---- opening b with defaults 992 x 1323 ----")
      b <- ChromoteSession$new()
      show_browser_size(b)
      show_window_inner_width(b)
    
      message("---- opening c at 450 x 900----")
      c <- ChromoteSession$new(width = 450, height = 900, new_window = FALSE)
      show_browser_size(b)
      show_window_inner_width(b)
      show_browser_size(c)
      show_window_inner_width(c)
    })
    #> ---- opening b with defaults 992 x 1323 ----
    #> `b` has window dimensions: 1200 x 1371
    #> `b` has window.inner{Width,Height}: 992 x 1323
    #> ---- opening c at 450 x 900----
    #> `b` has window dimensions: 1200 x 1371
    #> `b` has window.inner{Width,Height}: 992 x 1323
    #> `c` has window dimensions: 1200 x 1371
    #> `c` has window.inner{Width,Height}: 450 x 900

@gadenbuie
Copy link
Member

@chlebowa As a quick way around this problem, you can use the dev version of chromote which has new support for managing Chrome installations. You can either use an older version of Chrome or any version of chrome-headless-shell. The second option would be my recommendation for now, especially if this is for app testing with shinytest2.

pak::pak("rstudio/chromote")

# Somewhere in your test setup
chromote::local_chrome_version("latest-stable", binary = "chrome-headless-shell")

# ... the rest of your app testing code ...

@chlebowa
Copy link
Author

chlebowa commented Mar 6, 2025

Many thanks for the quick reaction @gadenbuie!
I thought about using the dev version but I saw that's it's 0.4.0.9000 and assumed there were no changes yet, I see now that the recent PRs were made without version bumps.

@gadenbuie
Copy link
Member

I thought about using the dev version but I saw that's it's 0.4.0.9000 and assumed there were no changes yet, I see now that the recent PRs were made without version bumps.

No problem! And yeah, our convention is to use .9000 as an umbrella for all changes after 0.4.0 but not yet formally released.

@gadenbuie
Copy link
Member

Oh and just to be clear, this change in behavior isn't driven by chromote; it's from changes in Chrome.

@fpahlke
Copy link

fpahlke commented Mar 7, 2025

@chlebowa As a quick way around this problem, you can use the dev version of chromote which has new support for managing Chrome installations. You can either use an older version of Chrome or any version of chrome-headless-shell. The second option would be my recommendation for now, especially if this is for app testing with shinytest2.

pak::pak("rstudio/chromote")

Somewhere in your test setup

chromote::local_chrome_version("latest-stable", binary = "chrome-headless-shell")

... the rest of your app testing code ...

Thank you very muach @gadenbuie ! I had the same issue and were able to solve it with the code above.

@chlebowa
Copy link
Author

chlebowa commented Mar 7, 2025

Oh and just to be clear, this change in behavior isn't driven by chromote; it's from changes in Chrome.

Yes, I suspected as much and it was clear in your answer. I decided to bring it to you because I assumed other people using chromote with the latest stable version of Chrome would have the same problem.

Again, thank you 👍

@chlebowa
Copy link
Author

chlebowa commented Mar 7, 2025

@chlebowa As a quick way around this problem

Worked like a charm 🌟

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants