% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/app-driver.R
\name{AppDriver}
\alias{AppDriver}
\title{Drive a Shiny application}
\description{
This class starts a Shiny app in a new R session, along with \pkg{chromote}'s
headless browser that can be used to simulate user actions. This provides
a full simulation of a Shiny app so that you can test user interactions
with a live app.

Methods described below are ordered by perceived popularity.
\emph{Expect} methods are grouped next to their corresponding \emph{get} methods.
}
\section{Vignettes}{


Please see \href{https://rstudio.github.io/shinytest2/articles/in-depth.html}{Testing in depth}
for more details about the different expectation methods.

Please see \href{https://rstudio.github.io/shinytest2/articles/robust.html}{Robust testing}
for more details about the cost / benefits for each expectation method.
}

\section{Test mode}{


To have your \code{AppDriver} retrieve values from your Shiny app, be sure to
set \code{shiny::runApp(test.mode = TRUE)} when running your Shiny app.

If you are deploying your Shiny app where you do not have control over
the call to \code{shiny::runApp()}, you can set \code{options(shiny.testmode = TRUE)} in
a \code{.Rprofile} file within your Shiny app directory.
}

\section{Start-up failure}{


If the app throws an error during initialization, the \code{AppDriver} will
be stored in \code{rlang::last_error()$app}. This allows for the "failure
to initialize" to be signaled while also allowing for the \code{app} to be
retrieved after any initialization error has been thrown.
}

\section{Exporting reactive values}{


Reactive values from within your Shiny application can be exported using the
method:
\href{https://shiny.posit.co/r/reference/shiny/latest/exporttestvalues.html}{\code{shiny::exportTestValues()}}.
This underutilized method exposes internal values of your app
without needing to create a corresponding input value or output value.

For example:

\if{html}{\out{<div class="sourceCode r">}}\preformatted{library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Pythagorean theorem"),
    numericInput("A", "A", 3),
    numericInput("B", "B", 4),
    verbatimTextOutput("C"),
  ),
  function(input, output) \{
    a_squared <- reactive(\{ req(input$A); input$A * input$A \})
    b_squared <- reactive(\{ req(input$B); input$B * input$B \})
    c_squared <- reactive(\{ a_squared() + b_squared() \})
    c_value <- reactive(\{ sqrt(c_squared()) \})
    output$C <- renderText(\{ c_value() \})

    exportTestValues(
      a_squared = \{ a_squared() \},
      b_squared = \{ b_squared() \},
      c_squared = \{ c_squared() \}
    )
  \}
)

app <- AppDriver$new(shiny_app)

init_vals <- app$get_values()
str(init_vals)
#> List of 3
#> $ input :List of 2
#> ..$ A: int 3
#> ..$ B: int 4
#> $ output:List of 1
#> ..$ C: chr "5"
#> $ export:List of 3
#> ..$ a_squared: int 9
#> ..$ b_squared: int 16
#> ..$ c_squared: int 25
}\if{html}{\out{</div>}}

These exported test values are only exposed when \code{shiny::runApp(test.mode = TRUE)}
is set.  \pkg{shinytest2} sets this variable when running Shiny based app or
document.
}

\section{\pkg{testthat} wrappers}{


The two main expectation methods: \verb{$expect_values()} and \verb{$expect_screenshot()}
eventually wrap \code{\link[testthat:expect_snapshot_file]{testthat::expect_snapshot_file()}}.

Their underlying logic is similar to:

\if{html}{\out{<div class="sourceCode r">}}\preformatted{skip_on_cran() # AppDriver

## Expect values
tmpfile <- tempfile(fileext = ".json")
jsonlite::write_json(app$get_values(), tmpfile)
expect_snapshot_file(
  tmpfile,
  variant = app$get_variant(),
  compare = testthat::compare_file_text
)


## Expect screenshot
tmpfile <- tempfile(fileext = ".png")
app$get_screenshot(tmpfile)
expect_snapshot_file(
  tmpfile,
  variant = app$get_variant(),
  compare = testthat::compare_file_binary
)
}\if{html}{\out{</div>}}

To update the snapshot values, you will need to run a variation of
\code{\link[testthat:snapshot_accept]{testthat::snapshot_review()}}.
}

\examples{

## ------------------------------------------------
## Method `AppDriver$new`
## ------------------------------------------------

\dontrun{
# Create an AppDriver from the Shiny app in the current directory
app <- AppDriver()

# Create an AppDriver object from a different Shiny app directory
example_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver(example_app)

# Expect consistent inital values
app$expect_values()
}

## ------------------------------------------------
## Method `AppDriver$view`
## ------------------------------------------------

\dontrun{
# Open app in Chrome
app$view()
}

## ------------------------------------------------
## Method `AppDriver$click`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

tmpfile <- write.csv(cars, "cars.csv")
app$upload_file(file1 = tmpfile)
cat(app$get_text("#view"))
app$set_inputs(dataset = "cars", obs = 6)
app$click("update")
cat(app$get_text("#view"))
}

## ------------------------------------------------
## Method `AppDriver$set_inputs`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

cat(app$get_text("#view"))
app$set_inputs(dataset = "cars", obs = 6)
app$click("update")
cat(app$get_text("#view"))
}

## ------------------------------------------------
## Method `AppDriver$upload_file`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/09_upload", package = "shiny")
app <- AppDriver$new(app_path)

# Save example file
tmpfile <- tempfile(fileext = ".csv")
write.csv(cars, tmpfile, row.names = FALSE)

# Upload file to input named: file1
app$upload_file(file1 = tmpfile)
}

## ------------------------------------------------
## Method `AppDriver$expect_values`
## ------------------------------------------------

\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Pythagorean theorem"),
    numericInput("A", "A", 3),
    numericInput("B", "B", 4),
    verbatimTextOutput("C"),
  ),
  function(input, output) {
    a_squared <- reactive({ req(input$A); input$A * input$A })
    b_squared <- reactive({ req(input$B); input$B * input$B })
    c_squared <- reactive({ a_squared() + b_squared() })
    c_value <- reactive({ sqrt(c_squared()) })
    output$C <- renderText({ c_value() })

    exportTestValues(
      a_squared = { a_squared() },
      b_squared = { b_squared() },
      c_squared = { c_squared() }
    )
  }
)

app <- AppDriver$new(shiny_app)

# Snapshot all known values
app$expect_values()

# Snapshot only `export` values
app$expect_values(export = TRUE)

# Snapshot values `"A"` from `input` and `"C"` from `output`
app$expect_values(input = "A", output = "C")
}

## ------------------------------------------------
## Method `AppDriver$get_value`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/04_mpg", package = "shiny")
app <- AppDriver$new(app_path)

# Retrieve a single value
app$get_value(output = "caption")
#> [1] "mpg ~ cyl"
# Equivalent code using `$get_values()`
app$get_values(output = "caption")$output$caption
#> [1] "mpg ~ cyl"
}

## ------------------------------------------------
## Method `AppDriver$get_values`
## ------------------------------------------------

\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Pythagorean theorem"),
    numericInput("A", "A", 3),
    numericInput("B", "B", 4),
    verbatimTextOutput("C"),
  ),
  function(input, output) {
    a_squared <- reactive({ req(input$A); input$A * input$A })
    b_squared <- reactive({ req(input$B); input$B * input$B })
    c_squared <- reactive({ a_squared() + b_squared() })
    c_value <- reactive({ sqrt(c_squared()) })
    output$C <- renderText({ c_value() })

    exportTestValues(
      a_squared = { a_squared() },
      b_squared = { b_squared() },
      c_squared = { c_squared() }
    )
  }
)

app <- AppDriver$new(shiny_app)

# Show all known values
str(app$get_values())
#> List of 3
#> $ input :List of 2
#> ..$ A: int 3
#> ..$ B: int 4
#> $ output:List of 1
#> ..$ C: chr "5"
#> $ export:List of 3
#> ..$ a_squared: int 9
#> ..$ b_squared: int 16
#> ..$ c_squared: int 25

# Get only `export` values
str(app$get_values(export = TRUE))
#> List of 1
#> $ export:List of 3
#> ..$ a_squared: int 9
#> ..$ b_squared: int 16
#> ..$ c_squared: int 25

# Get values `"A"` from `input` and `"C"` from `output`
str(app$get_values(input = "A", output = "C"))
#> List of 2
#> $ input :List of 1
#> ..$ A: int 3
#> $ output:List of 1
#> ..$ C: chr "5"
}

## ------------------------------------------------
## Method `AppDriver$expect_download`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/10_download", package = "shiny")
app <- AppDriver$new(app_path)

# Save snapshot of rock.csv as 001.download
# Save snapshot value of `rock.csv` to capture default file name
app$expect_download("downloadData", compare = testthat::compare_file_text)
}

## ------------------------------------------------
## Method `AppDriver$get_download`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/10_download", package = "shiny")
app <- AppDriver$new(app_path)

# Get rock.csv as a tempfile
app$get_download("downloadData")
#> [1] "/TEMP/PATH/rock.csv"

# Get rock.csv as a "./myfile.csv"
app$get_download("downloadData", filename = "./myfile.csv")
#> [1] "./myfile.csv"
}

## ------------------------------------------------
## Method `AppDriver$expect_text`
## ------------------------------------------------

\dontrun{
hello_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(hello_app)

# Make a snapshot of `"Hello Shiny!"`
app$expect_text("h2")
}

## ------------------------------------------------
## Method `AppDriver$get_text`
## ------------------------------------------------

\dontrun{
hello_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(hello_app)

app$get_text("h2")
#> [1] "Hello Shiny!"
}

## ------------------------------------------------
## Method `AppDriver$expect_html`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/04_mpg", package = "shiny")
app <- AppDriver$new(app_path)

# Save a snapshot of the `caption` output
app$expect_html("#caption")
}

## ------------------------------------------------
## Method `AppDriver$get_html`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/03_reactivity", package = "shiny")
app <- AppDriver$new(app_path, check_names = FALSE)

app$set_inputs(caption = "Custom value!")
cat(app$get_html(".shiny-input-container")[1])
#> <div class="form-group shiny-input-container">
#>   <label class="control-label" id="caption-label" for="caption">Caption:</label>
#>   <input id="caption" type="text" class="form-control shiny-bound-input" value="Data Summary">
#> </div>
## ^^ No update to the DOM of `caption`
}

## ------------------------------------------------
## Method `AppDriver$expect_js`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

# Track how many clicks are given to `#update` button
app$run_js("
  window.test_counter = 0;
  $('#update').click(() => window.test_counter++);
")
app$set_inputs(obs = 20)
# Click the update button, incrementing the counter
app$click("update")
# Save a snapshot of number of clicks (1)
app$expect_js("window.test_counter;")
}

## ------------------------------------------------
## Method `AppDriver$get_js`
## ------------------------------------------------

\dontrun{
library(shiny)
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Execute JavaScript code in the app's browser
app$get_js("1 + 1;")
#> [1] 2

# Execute a JavaScript Promise. Return the resolved value.
app$get_js("
  new Promise((resolve) => {
    setTimeout(() => resolve(1 + 1), 1000)
  }).
  then((value) => value + 1);
")
#> [1] 3

# With escaped arguments
loc_field <- "hostname"
js_txt <- paste0("window.location[", jsonlite::toJSON(loc_field, auto_unbox = TRUE), "]")
app$get_js(js_txt)
#> [1] "127.0.0.1"

# With `glue::glue()`
js_txt <- glue::glue_data(
  lapply(
    list(x = 40, y = 2),
    jsonlite::toJSON,
    auto_unbox = TRUE
  ),
  .open = "<", .close = ">",
  "let answer = function(a, b) {\n",
  "  return a + b;\n",
  "};\n",
  "answer(<x>, <y>);\n"
)
app$get_js(js_txt)
#> [1] 42
}

## ------------------------------------------------
## Method `AppDriver$run_js`
## ------------------------------------------------

\dontrun{
library(shiny)
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Get JavaScript answer from the app's browser
app$get_js("1 + 1")
#> [1] 2
# Execute JavaScript code in the app's browser
app$run_js("1 + 1")
# (Returns `app` invisibly)

# With escaped arguments
loc_field <- "hostname"
js_txt <- paste0("window.location[", jsonlite::toJSON(loc_field, auto_unbox = TRUE), "]")
app$run_js(js_txt)
app$get_js(js_txt)
#> [1] "127.0.0.1"
}

## ------------------------------------------------
## Method `AppDriver$expect_screenshot`
## ------------------------------------------------

\dontrun{
# These example lines should be performed in a `./tests/testthat`
# test file so that snapshot files can be properly saved

app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path, variant = platform_variant())

# Expect a full size screenshot to be pixel perfect
app$expect_screenshot()

# Images are brittle when containing plots
app$expect_screenshot(selector = "#distPlot")

# Test with more threshold in pixel value differences
# Helps with rounded corners
app$expect_screenshot(threshold = 10)

# Equivalent expectations
app$expect_screenshot() # default
app$expect_screenshot(threshold = NULL)
app$expect_screenshot(compare = testthat::compare_file_binary)
expect_snapshot_file(
  app$get_screenshot(),
  variant = app$get_variant(),
  compare = testthat::compare_file_binary
)

# Equivalent expectations
app$expect_screenshot(threshold = 3, kernel_size = 5)
app$expect_screenshot(compare = function(old, new) {
  compare_screenshot_threshold(
    old, new,
    threshold = 3,
    kernel_size = 5
  )
})
expect_screenshot_file(
  app$get_screenshot(),
  variant = app$get_variant(),
  compare = function(old, new) {
    compare_screenshot_threshold(
      old, new,
      threshold = 3,
      kernel_size = 5
    )
  }
)

# Take a screenshot of the entire scrollable area
app$expect_screenshot()
app$expect_screenshot(selector = "scrollable_area")

## Take a screenshot of the current viewport
# Shrink the window to be smaller than the app
app$set_window_size(400, 500)
# Scroll the viewport just a bit
app$run_js("window.scroll(30, 70)")
# Take screenshot of browser viewport
app$expect_screenshot(selector = "viewport")
}

## ------------------------------------------------
## Method `AppDriver$get_screenshot`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

# Display in graphics device
app$get_screenshot()

# Update bins then display `"disPlot"` in graphics device
app$set_inputs(bins = 10)
app$get_screenshot(selector = "#distPlot")

# Save screenshot to file and view it
tmpfile <- tempfile(fileext = ".png")
app$get_screenshot(tmpfile)
showimage::show_image(tmpfile)
}

## ------------------------------------------------
## Method `AppDriver$wait_for_idle`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

pre_value <- app$get_value(output = "distPlot")
# Update bins value
app$set_inputs(bins = 10, wait_ = FALSE)
middle_value <- app$get_value(output = "distPlot")
app$wait_for_idle()
post_value <- app$get_value(output = "distPlot")

# No guarantee that these values are different
identical(pre_value, middle_value)
# Will not be equal
identical(pre_value, post_value)

# ---------------------
## Change the screen size to trigger a plot update
pre_value <- app$get_value(output = "distPlot")
app$set_window_size(height = 1080, width = 1920, wait = FALSE)
middle_value <- app$get_value(output = "distPlot")
app$wait_for_idle()
post_value <- app$get_value(output = "distPlot")

# No guarantee that these values are different
identical(pre_value, middle_value)
# Will not be equal
identical(pre_value, post_value)
}

## ------------------------------------------------
## Method `AppDriver$wait_for_value`
## ------------------------------------------------

\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Dynamic output"),
    actionButton("display", "Display UI"),
    uiOutput("dym1"),
  ),
  function(input, output) {
    output$dym1 <- renderUI({
      req(input$display)
      Sys.sleep(runif(1, max = 2)) # Artificial calculations
      tagList(
        sliderInput("slider1", "Slider #1", 0, 100, 25),
        uiOutput("dym2")
      )
    })
    output$dym2 <- renderUI({
      Sys.sleep(runif(1, max = 2)) # Artificial calculations
      tagList(
        sliderInput("slider2", "Slider #2", 0, 100, 50),
        "Total:", verbatimTextOutput("total")
      )
    })
    output$total <- renderText({
      req(input$slider1, input$slider2)
      input$slider1 + input$slider2
    })
  }
)

app <- AppDriver$new(shiny_app)

# Create UI / output values
app$click("display")
# Wait for total to be calculated (or have a non-NULL value)
new_total_value <- app$wait_for_value(output = "total")
#> [1] "75"
app$get_value(output = "total")
#> [1] "75"
}

## ------------------------------------------------
## Method `AppDriver$wait_for_js`
## ------------------------------------------------

\dontrun{
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Contrived example:
# Wait until `Date.now()` returns a number that ends in a 5. (0 - 10 seconds)
system.time(
  app$wait_for_js("Math.floor((Date.now() / 1000) \% 10) == 5;")
)

## A second example where we run the contents of a JavaScript file
## and use the result to wait for a condition
app$run_js(file = "complicated_file.js")
app$wait_for_js("complicated_condition();")
}

## ------------------------------------------------
## Method `AppDriver$expect_unique_names`
## ------------------------------------------------

\dontrun{
shiny_app <- shinyApp(
  ui = fluidPage(
    # Duplicate input IDs: `"text"`
    textInput("text", "Text 1"),
    textInput("text", "Text 2")
  ),
  server = function(input, output) {
    # empty
  }
)
# Initial checking for unique names (default behavior)
app <- AppDriver$new(shiny_app, check_names = TRUE)
#> Warning:
#> ! Shiny inputs should have unique HTML id values.
#> i The following HTML id values are not unique:
#> • text
app$stop()

# Manually assert that all names are unique
app <- AppDriver$new(shiny_app, check_names = FALSE)
app$expect_unique_names()
#> Error: `app_check_unique_names(self, private)` threw an unexpected warning.
#> Message: ! Shiny inputs should have unique HTML id values.
#> i The following HTML id values are not unique:
#>   • text
#> Class:   rlang_warning/warning/condition
app$stop()
}

## ------------------------------------------------
## Method `AppDriver$get_dir`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

identical(app$get_dir(), app_path)
#> [1] TRUE
}

## ------------------------------------------------
## Method `AppDriver$get_url`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

browseURL(app$get_url())
}

## ------------------------------------------------
## Method `AppDriver$get_window_size`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

app$get_window_size()
#> $width
#> [1] 992
#>
#> $height
#> [1] 1323
}

## ------------------------------------------------
## Method `AppDriver$set_window_size`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
# Set init window size
app <- AppDriver$new(app_path, height = 1400, width = 1000)

app$get_window_size()
#> $width
#> [1] 1000
#>
#> $height
#> [1] 1400

# Manually set the window size
app$set_window_size(height = 1080, width = 1920)
app$get_window_size()
#> $width
#> [1] 1920
#>
#> $height
#> [1] 1080
}

## ------------------------------------------------
## Method `AppDriver$get_chromote_session`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

b <- app$get_chromote_session()
b$Runtime$evaluate("1 + 1")
#> $result
#> $result$type
#> [1] "number"
#>
#> $result$value
#> [1] 2
#>
#> $result$description
#> [1] "2"
}

## ------------------------------------------------
## Method `AppDriver$get_variant`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")

app <- AppDriver$new(app_path)

app$get_variant()
#> NULL

app <- AppDriver$new(app_path, variant = platform_variant())
app$get_variant()
#> [1] "mac-4.1"
}

## ------------------------------------------------
## Method `AppDriver$get_logs`
## ------------------------------------------------

\dontrun{
app1 <- AppDriver$new(system.file("examples/01_hello", package = "shiny"))

app1$get_logs()
#> \{shinytest2\} R  info   10:00:28.86 Start AppDriver initialization
#> \{shinytest2\} R  info   10:00:28.86 Starting Shiny app
#> \{shinytest2\} R  info   10:00:29.76 Creating new ChromoteSession
#> \{shinytest2\} R  info   10:00:30.56 Navigating to Shiny app
#> \{shinytest2\} R  info   10:00:30.70 Injecting shiny-tracer.js
#> \{chromote\}   JS info   10:00:30.75 shinytest2; jQuery found
#> \{chromote\}   JS info   10:00:30.77 shinytest2; Waiting for shiny session to connect
#> \{chromote\}   JS info   10:00:30.77 shinytest2; Loaded
#> \{shinytest2\} R  info   10:00:30.77 Waiting for Shiny to become ready
#> \{chromote\}   JS info   10:00:30.90 shinytest2; Connected
#> \{chromote\}   JS info   10:00:30.95 shinytest2; shiny:busy
#> \{shinytest2\} R  info   10:00:30.98 Waiting for Shiny to become idle for 200ms within 15000ms
#> \{chromote\}   JS info   10:00:30.98 shinytest2; Waiting for Shiny to be stable
#> \{chromote\}   JS info   10:00:31.37 shinytest2; shiny:idle
#> \{chromote\}   JS info   10:00:31.38 shinytest2; shiny:value distPlot
#> \{chromote\}   JS info   10:00:31.57 shinytest2; Shiny has been idle for 200ms
#> \{shinytest2\} R  info   10:00:31.57 Shiny app started
#> \{shiny\}      R  stderr ----------- Loading required package: shiny
#> \{shiny\}      R  stderr ----------- Running application in test mode.
#> \{shiny\}      R  stderr -----------
#> \{shiny\}      R  stderr ----------- Listening on http://127.0.0.1:4679


# To capture all websocket traffic, set `options = list(shiny.trace = TRUE)`
app2 <- AppDriver$new(
  system.file("examples/01_hello", package = "shiny"),
  options = list(shiny.trace = TRUE)
)

app2$get_logs()
## (All WebSocket messages have been replaced with `WEBSOCKET_MSG` in example below)
#> \{shinytest2\} R  info      10:01:57.49 Start AppDriver initialization
#> \{shinytest2\} R  info      10:01:57.50 Starting Shiny app
#> \{shinytest2\} R  info      10:01:58.20 Creating new ChromoteSession
#> \{shinytest2\} R  info      10:01:58.35 Navigating to Shiny app
#> \{shinytest2\} R  info      10:01:58.47 Injecting shiny-tracer.js
#> \{chromote\}   JS info      10:01:58.49 shinytest2; jQuery not found
#> \{chromote\}   JS info      10:01:58.49 shinytest2; Loaded
#> \{shinytest2\} R  info      10:01:58.50 Waiting for Shiny to become ready
#> \{chromote\}   JS info      10:01:58.55 shinytest2; jQuery found
#> \{chromote\}   JS info      10:01:58.55 shinytest2; Waiting for shiny session to connect
#> \{chromote\}   JS websocket 10:01:58.64 send WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.67 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.67 shinytest2; Connected
#> \{chromote\}   JS websocket 10:01:58.71 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.72 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.72 shinytest2; shiny:busy
#> \{chromote\}   JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{shinytest2\} R  info      10:01:58.75 Waiting for Shiny to become idle for 200ms within 15000ms
#> \{chromote\}   JS info      10:01:58.75 shinytest2; Waiting for Shiny to be stable
#> \{chromote\}   JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.81 shinytest2; shiny:idle
#> \{chromote\}   JS websocket 10:01:58.82 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.82 shinytest2; shiny:value distPlot
#> \{chromote\}   JS info      10:01:59.01 shinytest2; Shiny has been idle for 200ms
#> \{shinytest2\} R  info      10:01:59.01 Shiny app started
#> \{shiny\}      R  stderr    ----------- Loading required package: shiny
#> \{shiny\}      R  stderr    ----------- Running application in test mode.
#> \{shiny\}      R  stderr    -----------
#> \{shiny\}      R  stderr    ----------- Listening on http://127.0.0.1:4560
#> \{shiny\}      R  stderr    ----------- SEND \{"config":\{"workerId":"","sessionId"|truncated
#> \{shiny\}      R  stderr    ----------- RECV \{"method":"init","data":\{"bins":30,|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"custom":\{"showcase-src":\{"srcref":|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"busy":"busy"\}
#> \{shiny\}      R  stderr    ----------- SEND \{"custom":\{"showcase-src":\{"srcref":|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"recalculating":\{"name":"distPlot",|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"recalculating":\{"name":"distPlot",|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"busy":"idle"\}
#> \{shiny\}      R  stderr    ----------- SEND \{"errors":\{\},"values":\{"distPlot":|truncated

# The log that is returned is a `data.frame()`.
log <- app2$get_logs()
tibble::glimpse(log)
#> Rows: 43
#> Columns: 5
#> $ workerid  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ timestamp <dttm> 2022-09-19 10:01:57, 2022-09-19 10:01:57, 2022-09-19 10:01:58, 2022…
#> $ location  <chr> "shinytest2", "shinytest2", "shinytest2", "shinytest2", "shinytest2"…
#> $ level     <chr> "info", "info", "info", "info", "info", "info", "info", "info", "inf…
#> $ message   <chr> "Start AppDriver initialization", "Starting Shiny app", "Creating ne…
#> $ workerid  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ timestamp <dttm> 2022-03-16 11:09:57, 2022-03-16 11:09:57, 2022-03-16 11:09:…
#> $ location  <chr> "shinytest2", "shinytest2", "shinytest2", "shinytest2", "shi…
#> $ level     <chr> "info", "info", "info", "info", "info", "info", "info", "inf…
#> $ message   <chr> "Start AppDriver initialization", "Starting Shiny app", "Cre…

# It may be filtered to find desired logs
subset(log, level == "websocket")
## (All WebSocket messages have been replaced with `WEBSOCKET_MSG` in example below)
#> \{chromote\} JS websocket 10:01:58.64 send WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.67 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.71 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.72 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.82 recv WEBSOCKET_MSG
}

## ------------------------------------------------
## Method `AppDriver$log_message`
## ------------------------------------------------

\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

app$log_message("Setting bins to smaller value")
app$set_inputs(bins = 10)
app$get_logs()
}

## ------------------------------------------------
## Method `AppDriver$stop`
## ------------------------------------------------

\dontrun{
rlang::check_installed("reactlog")

library(shiny)
shiny_app <- shinyApp(
  ui = fluidPage(
    actionButton("button", "Stop app and return Reactlog"),
    "Click count:", textOutput("count")
  ),
  server = function(input, output) {
    output$count <- renderText({ input$button })
    observe({
      req(input$button)
      stopApp(shiny::reactlog())
    })
  }
)

app <- AppDriver$new(
  shiny_app,
  # Enable reactlog in background R session
  options = list(shiny.reactlog = TRUE)
)

app$click("button")
rlog <- app$stop()
str(head(rlog, 2))
#> List of 2
#> $ :List of 7
#> ..$ action : chr "define"
#> ..$ reactId: chr "r3"
#> ..$ label  : chr "Theme Counter"
#> ..$ type   : chr "reactiveVal"
#> ..$ value  : chr " num 0"
#> ..$ session: chr "bdc7417f2fc8c84fc05c9518e36fdc44"
#> ..$ time   : num 1.65e+09
#> $ :List of 7
#> ..$ action : chr "define"
#> ..$ reactId: chr "r4"
#> ..$ label  : chr "output$count"
#> .. ..- attr(*, "srcref")= int [1:6] 7 32 7 45 32 45
#> .. ..- attr(*, "srcfile")= chr ""
#> ..$ type   : chr "observer"
#> ..$ value  : chr " NULL"
#> ..$ session: chr "bdc7417f2fc8c84fc05c9518e36fdc44"
#> ..$ time   : num 1.65e+09
}
}
\seealso{
\code{\link[=platform_variant]{platform_variant()}}, \code{\link[=use_shinytest2_test]{use_shinytest2_test()}}
}
\section{Methods}{
\subsection{Public methods}{
\itemize{
\item \href{#method-AppDriver-new}{\code{AppDriver$new()}}
\item \href{#method-AppDriver-view}{\code{AppDriver$view()}}
\item \href{#method-AppDriver-click}{\code{AppDriver$click()}}
\item \href{#method-AppDriver-set_inputs}{\code{AppDriver$set_inputs()}}
\item \href{#method-AppDriver-upload_file}{\code{AppDriver$upload_file()}}
\item \href{#method-AppDriver-expect_values}{\code{AppDriver$expect_values()}}
\item \href{#method-AppDriver-get_value}{\code{AppDriver$get_value()}}
\item \href{#method-AppDriver-get_values}{\code{AppDriver$get_values()}}
\item \href{#method-AppDriver-expect_download}{\code{AppDriver$expect_download()}}
\item \href{#method-AppDriver-get_download}{\code{AppDriver$get_download()}}
\item \href{#method-AppDriver-expect_text}{\code{AppDriver$expect_text()}}
\item \href{#method-AppDriver-get_text}{\code{AppDriver$get_text()}}
\item \href{#method-AppDriver-expect_html}{\code{AppDriver$expect_html()}}
\item \href{#method-AppDriver-get_html}{\code{AppDriver$get_html()}}
\item \href{#method-AppDriver-expect_js}{\code{AppDriver$expect_js()}}
\item \href{#method-AppDriver-get_js}{\code{AppDriver$get_js()}}
\item \href{#method-AppDriver-run_js}{\code{AppDriver$run_js()}}
\item \href{#method-AppDriver-expect_screenshot}{\code{AppDriver$expect_screenshot()}}
\item \href{#method-AppDriver-get_screenshot}{\code{AppDriver$get_screenshot()}}
\item \href{#method-AppDriver-wait_for_idle}{\code{AppDriver$wait_for_idle()}}
\item \href{#method-AppDriver-wait_for_value}{\code{AppDriver$wait_for_value()}}
\item \href{#method-AppDriver-wait_for_js}{\code{AppDriver$wait_for_js()}}
\item \href{#method-AppDriver-expect_unique_names}{\code{AppDriver$expect_unique_names()}}
\item \href{#method-AppDriver-get_dir}{\code{AppDriver$get_dir()}}
\item \href{#method-AppDriver-get_url}{\code{AppDriver$get_url()}}
\item \href{#method-AppDriver-get_window_size}{\code{AppDriver$get_window_size()}}
\item \href{#method-AppDriver-set_window_size}{\code{AppDriver$set_window_size()}}
\item \href{#method-AppDriver-get_chromote_session}{\code{AppDriver$get_chromote_session()}}
\item \href{#method-AppDriver-get_variant}{\code{AppDriver$get_variant()}}
\item \href{#method-AppDriver-get_logs}{\code{AppDriver$get_logs()}}
\item \href{#method-AppDriver-log_message}{\code{AppDriver$log_message()}}
\item \href{#method-AppDriver-stop}{\code{AppDriver$stop()}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-new"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-new}{}}}
\subsection{Method \code{new()}}{
Initialize an \code{AppDriver} object
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$new(
  app_dir = testthat::test_path("../../"),
  ...,
  name = NULL,
  variant = missing_arg(),
  seed = NULL,
  load_timeout = missing_arg(),
  timeout = missing_arg(),
  wait = TRUE,
  screenshot_args = missing_arg(),
  expect_values_screenshot_args = TRUE,
  check_names = TRUE,
  view = missing_arg(),
  height = NULL,
  width = NULL,
  clean_logs = TRUE,
  shiny_args = list(),
  render_args = NULL,
  options = list()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{app_dir}}{This value can be many different things:
\itemize{
\item A directory containing your Shiny application or a run-time Shiny R
Markdown document.
\item A URL pointing to your shiny application. (Don't forget to set
\code{testmode = TRUE} when running your application!)
\item A Shiny application object which inherits from \code{"shiny.appobj"}.
}

By default, \code{app_dir} is set to \code{test_path("../../")} to work in both
interactive and testing usage.

If a file path is not provided to \code{app_dir}, snapshots will be saved as
if the root of the Shiny application was the current directory.}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{name}}{Prefix value to use when saving testthat snapshot files. Ex:
\verb{NAME-001.json}. Name \strong{must} be unique when saving multiple snapshots
from within the same testing file. Otherwise, two different \code{AppDriver}
objects will be referencing the same files.}

\item{\code{variant}}{If not-\code{NULL}, results will be saved in
\verb{_snaps/\{variant\}/\{test.md\}}, so \code{variant} must be a single
string of alphanumeric characters suitable for use as a
directory name.

You can variants to deal with cases where the snapshot output
varies and you want to capture and test the variations.
Common use cases include variations for operating system, R
version, or version of key dependency. For example usage,
see \code{\link[=platform_variant]{platform_variant()}}.}

\item{\code{seed}}{An optional random seed to use before starting the application.
For apps that use R's random number generator, this can make their
behavior repeatable.}

\item{\code{load_timeout}}{How long to wait for the app to load, in ms.
This includes the time to start R. Defaults to 15s.

If \code{load_timeout} is missing, the first numeric value found will be used:
\itemize{
\item R option \code{options(shinytest2.load_timeout=)}
\item System environment variable \code{SHINYTEST2_LOAD_TIMEOUT}
\item \code{15 * 1000} (15 seconds)
}}

\item{\code{timeout}}{Default number of milliseconds for any \code{timeout} or
\code{timeout_} parameter in the \code{AppDriver} class. Defaults to 4s.

If \code{timeout} is missing, the first numeric value found will be used:
\itemize{
\item R option \code{options(shinytest2.timeout=)}
\item System environment variable \code{SHINYTEST2_TIMEOUT}
\item \code{4 * 1000} (4 seconds)
}}

\item{\code{wait}}{If \code{TRUE}, \verb{$wait_for_idle(duration = 200, timeout = load_timeout)}
will be called once the app has connected a new session, blocking until the
Shiny app is idle for 200ms.}

\item{\code{screenshot_args}}{Default set of arguments to pass in to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method when taking
screenshots within \verb{$expect_screenshot()}. To disable screenshots by
default, set to \code{FALSE}.}

\item{\code{expect_values_screenshot_args}}{The value for \code{screenshot_args} when
producing a debug screenshot for \verb{$expect_values()}. To disable debug
screenshots by default, set to \code{FALSE}.}

\item{\code{check_names}}{Check if widget names are unique once the application
initially loads? If duplicate names are found on initialization, a
warning will be displayed.}

\item{\code{view}}{Opens the \code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}} in an interactive browser tab
before attempting to navigate to the Shiny app.}

\item{\code{height, width}}{Window size to use when opening the
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}. Both \code{height} and \code{width} values must be non-null
values to be used.}

\item{\code{clean_logs}}{Whether to remove the \code{stdout} and \code{stderr} Shiny app
logs when the \code{AppDriver} object is garbage collected.}

\item{\code{shiny_args}}{A list of options to pass to \code{\link[shiny:runApp]{shiny::runApp()}}. Ex:
\code{list(port = 8080)}.}

\item{\code{render_args}}{Passed to \code{rmarkdown::run(render_args=)} for
interactive \code{.Rmd}s. Ex: \code{list(quiet = TRUE)}}

\item{\code{options}}{A list of \code{\link[base:options]{base::options()}} to set in the Shiny
application's child R process. See \code{\link[shiny:shinyOptions]{shiny::shinyOptions()}} for
inspiration. If \code{shiny.trace = TRUE}, then all WebSocket traffic will
be captured by \code{chromote} and time-stamped for logging purposes.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
An object with class \code{AppDriver} and the many methods described in this documentation.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
# Create an AppDriver from the Shiny app in the current directory
app <- AppDriver()

# Create an AppDriver object from a different Shiny app directory
example_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver(example_app)

# Expect consistent inital values
app$expect_values()
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-view"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-view}{}}}
\subsection{Method \code{view()}}{
View the Shiny application

Calls \verb{$view()} on the \code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}} object to \emph{view} your Shiny
application in a Chrome browser.

This method is very helpful for debugging while writing your tests.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$view()}\if{html}{\out{</div>}}
}

\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
# Open app in Chrome
app$view()
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-click"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-click}{}}}
\subsection{Method \code{click()}}{
Click an element

Find a Shiny input/output value or DOM CSS selector and click it using
the \href{https://www.w3schools.com/jsref/met_html_click.asp}{DOM method \code{TAG.click()}}.

This method can be used to click input buttons and other elements that
need to simulate a click action.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$click(
  input = missing_arg(),
  output = missing_arg(),
  selector = missing_arg(),
  ...
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{input, output, selector}}{A name of an Shiny \code{input}/\code{output} value or
a DOM CSS selector. Only one of these may be used.}

\item{\code{...}}{If \code{input} is used, all extra arguments are passed to
\verb{$set_inputs(!!input := "click", ...)}. This means that the
\code{AppDriver} will wait until an output has been updated within the
specified \code{timeout_}. When clicking any other content, \code{...} must be empty.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

tmpfile <- write.csv(cars, "cars.csv")
app$upload_file(file1 = tmpfile)
cat(app$get_text("#view"))
app$set_inputs(dataset = "cars", obs = 6)
app$click("update")
cat(app$get_text("#view"))
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-set_inputs"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-set_inputs}{}}}
\subsection{Method \code{set_inputs()}}{
Set input values

Set Shiny inputs by sending the value to the Chrome browser and
programmatically updating the values. Given \code{wait_ = TRUE}, the method will
not return until an output value has been updated.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$set_inputs(
  ...,
  wait_ = TRUE,
  timeout_ = missing_arg(),
  allow_no_input_binding_ = FALSE,
  priority_ = c("input", "event")
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Name-value pairs, \verb{component_name_1 = value_1, component_name_2 = value_2} etc.
Input with name \code{component_name_1} will be assigned value \code{value_1}.}

\item{\code{wait_}}{Wait until all reactive updates have completed?}

\item{\code{timeout_}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}

\item{\code{allow_no_input_binding_}}{When setting the value of an input, allow
it to set the value of an input even if that input does not have an
input binding. This is useful to replicate behavior like hovering over
a \pkg{plotly} plot.}

\item{\code{priority_}}{Sets the event priority. For expert use only: see
\url{https://shiny.rstudio.com/articles/communicating-with-js.html#values-vs-events} for details.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

cat(app$get_text("#view"))
app$set_inputs(dataset = "cars", obs = 6)
app$click("update")
cat(app$get_text("#view"))
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-upload_file"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-upload_file}{}}}
\subsection{Method \code{upload_file()}}{
Upload a file

Uploads a file to the specified file input.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$upload_file(..., wait_ = TRUE, timeout_ = missing_arg())}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Name-path pair, e.g. \code{component_name = file_path}. The file located at
\code{file_path} will be uploaded to file input with name \code{component_name}.}

\item{\code{wait_}}{Wait until all reactive updates have completed?}

\item{\code{timeout_}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/09_upload", package = "shiny")
app <- AppDriver$new(app_path)

# Save example file
tmpfile <- tempfile(fileext = ".csv")
write.csv(cars, tmpfile, row.names = FALSE)

# Upload file to input named: file1
app$upload_file(file1 = tmpfile)
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_values"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_values}{}}}
\subsection{Method \code{expect_values()}}{
Expect \code{input}, \code{output}, and \code{export} values

A JSON snapshot is saved of given the results from the underlying call to \verb{$get_values()}.

Note, values that contain environments or other values that will have
trouble serializing may not work well. Instead, these objects should be
manually inspected and have their components tested individually.

Please see \href{https://rstudio.github.io/shinytest2/articles/robust.html}{Robust testing} for more details.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_values(
  ...,
  input = missing_arg(),
  output = missing_arg(),
  export = missing_arg(),
  screenshot_args = missing_arg(),
  name = NULL,
  transform = transform,
  cran = deprecated()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{input, output, export}}{Depending on which parameters are supplied, different return values can occur:
* If \code{input}, \code{output}, and \code{export} are all missing, then all values are included in the snapshot.
* If at least one \code{input}, \code{output}, or \code{export} is specified, then only the requested values are included in the snapshot.

The values supplied to each variable can be:
* A character vector of specific names to only include in the snapshot.
* \code{TRUE} to request that all values of that type are included in the snapshot.
* Anything else (e.g. \code{NULL} or \code{FALSE}) will result in the parameter being ignored.}

\item{\code{screenshot_args}}{This value is passed along to
\verb{$expect_screenshot()} where the resulting snapshot expectation is ignored. If
missing, the default value will be
\verb{$new(expect_values_screenshot_args=)}.

The final value can either be:
\itemize{
\item \code{TRUE}: A screenshot of the browser's scrollable area will be taken with no delay
\item \code{FALSE}: No screenshot will be taken
\item A named list of arguments. These arguments are passed directly to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method. The \code{selector}
and \code{delay} will default to \code{"html"} and \code{0} respectively.
}}

\item{\code{name}}{The file name to be used for the snapshot. The file extension
will be overwritten to \code{.json}. By default, the \code{name} supplied to
\code{app} on initialization with a counter will be used (e.g. \code{"NAME-001.json"}).}

\item{\code{transform}}{Optionally, a function to scrub sensitive or stochastic text
from the output. Should take a character vector of lines as input and
return a modified character vector as output.}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
The result of the snapshot expectation
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Pythagorean theorem"),
    numericInput("A", "A", 3),
    numericInput("B", "B", 4),
    verbatimTextOutput("C"),
  ),
  function(input, output) {
    a_squared <- reactive({ req(input$A); input$A * input$A })
    b_squared <- reactive({ req(input$B); input$B * input$B })
    c_squared <- reactive({ a_squared() + b_squared() })
    c_value <- reactive({ sqrt(c_squared()) })
    output$C <- renderText({ c_value() })

    exportTestValues(
      a_squared = { a_squared() },
      b_squared = { b_squared() },
      c_squared = { c_squared() }
    )
  }
)

app <- AppDriver$new(shiny_app)

# Snapshot all known values
app$expect_values()

# Snapshot only `export` values
app$expect_values(export = TRUE)

# Snapshot values `"A"` from `input` and `"C"` from `output`
app$expect_values(input = "A", output = "C")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_value"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_value}{}}}
\subsection{Method \code{get_value()}}{
Get a single \code{input}, \code{output}, or \code{export} value

This is a helper function around \verb{$get_values()} to retrieve a single
\code{input}, \code{output}, or \code{export} value. Only a single \code{input}, \code{output}, or
\code{export} value can be used.

Note, values that contain environments or other values that will have
trouble serializing to RDS may not work well.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_value(
  ...,
  input = missing_arg(),
  output = missing_arg(),
  export = missing_arg(),
  hash_images = FALSE
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{input, output, export}}{One of these variable should contain a single
string value. If more than one value is specified or no values are
specified, an error will be thrown.}

\item{\code{hash_images}}{If \code{TRUE}, images will be hashed before being returned.
Otherwise, all images will return their full data64 encoded value.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
The requested \code{input}, \code{output}, or \code{export} value.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/04_mpg", package = "shiny")
app <- AppDriver$new(app_path)

# Retrieve a single value
app$get_value(output = "caption")
#> [1] "mpg ~ cyl"
# Equivalent code using `$get_values()`
app$get_values(output = "caption")$output$caption
#> [1] "mpg ~ cyl"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_values"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_values}{}}}
\subsection{Method \code{get_values()}}{
Get \code{input}, \code{output}, and \code{export} values

Retrieves a list of all known \code{input}, \code{output}, or \code{export} values. This
method is a core method when inspecting your Shiny app.

Note, values that contain environments or other values that will have
trouble serializing may not work well.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_values(
  ...,
  input = missing_arg(),
  output = missing_arg(),
  export = missing_arg(),
  hash_images = FALSE
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{input, output, export}}{Depending on which parameters are supplied, different return values can occur:
* If \code{input}, \code{output}, and \code{export} are all missing, then all values are included in the snapshot.
* If at least one \code{input}, \code{output}, or \code{export} is specified, then only the requested values are included in the snapshot.

The values supplied to each variable can be:
* A character vector of specific names to only include in the snapshot.
* \code{TRUE} to request that all values of that type are included in the snapshot.
* Anything else (e.g. \code{NULL} or \code{FALSE}) will result in the parameter being ignored.}

\item{\code{hash_images}}{If \code{TRUE}, images will be hashed before being returned.
Otherwise, all images will return their full data64 encoded value.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
A named list of all inputs, outputs, and export values.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Pythagorean theorem"),
    numericInput("A", "A", 3),
    numericInput("B", "B", 4),
    verbatimTextOutput("C"),
  ),
  function(input, output) {
    a_squared <- reactive({ req(input$A); input$A * input$A })
    b_squared <- reactive({ req(input$B); input$B * input$B })
    c_squared <- reactive({ a_squared() + b_squared() })
    c_value <- reactive({ sqrt(c_squared()) })
    output$C <- renderText({ c_value() })

    exportTestValues(
      a_squared = { a_squared() },
      b_squared = { b_squared() },
      c_squared = { c_squared() }
    )
  }
)

app <- AppDriver$new(shiny_app)

# Show all known values
str(app$get_values())
#> List of 3
#> $ input :List of 2
#> ..$ A: int 3
#> ..$ B: int 4
#> $ output:List of 1
#> ..$ C: chr "5"
#> $ export:List of 3
#> ..$ a_squared: int 9
#> ..$ b_squared: int 16
#> ..$ c_squared: int 25

# Get only `export` values
str(app$get_values(export = TRUE))
#> List of 1
#> $ export:List of 3
#> ..$ a_squared: int 9
#> ..$ b_squared: int 16
#> ..$ c_squared: int 25

# Get values `"A"` from `input` and `"C"` from `output`
str(app$get_values(input = "A", output = "C"))
#> List of 2
#> $ input :List of 1
#> ..$ A: int 3
#> $ output:List of 1
#> ..$ C: chr "5"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_download"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_download}{}}}
\subsection{Method \code{expect_download()}}{
Expect a downloadable file

Given a \code{\link[shiny:downloadButton]{shiny::downloadButton()}}/\code{\link[shiny:downloadButton]{shiny::downloadLink()}} \code{output} ID, the corresponding
file will be downloaded and saved as a snapshot file.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_download(
  output,
  ...,
  compare = NULL,
  name = NULL,
  transform = NULL,
  cran = deprecated()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{output}}{\code{output} ID of \code{\link[shiny:downloadButton]{shiny::downloadButton()}}/\code{\link[shiny:downloadButton]{shiny::downloadLink()}}}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{compare}}{This value is passed through to \code{\link[testthat:expect_snapshot_file]{testthat::expect_snapshot_file()}}.
By default it is set to \code{NULL} which will default to \code{testthat::compare_file_text} if \code{name}
has extension \code{.r}, \code{.R}, \code{.Rmd}, \code{.md}, or \code{.txt}, and otherwise uses
\code{testthat::compare_file_binary}.}

\item{\code{name}}{File name to save file to (including file name extension). The default, \code{NULL},
generates an ascending sequence of names: \verb{001.download},
\verb{002.download}, etc.}

\item{\code{transform}}{Optionally, a function to scrub sensitive or stochastic text
from the output. Should take a character vector of lines as input and
return a modified character vector as output.}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/10_download", package = "shiny")
app <- AppDriver$new(app_path)

# Save snapshot of rock.csv as 001.download
# Save snapshot value of `rock.csv` to capture default file name
app$expect_download("downloadData", compare = testthat::compare_file_text)
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_download"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_download}{}}}
\subsection{Method \code{get_download()}}{
Get downloadable file

Given a \code{\link[shiny:downloadButton]{shiny::downloadButton()}}/\code{\link[shiny:downloadButton]{shiny::downloadLink()}} \code{output} ID, the corresponding
file will be downloaded and saved as a file.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_download(output, filename = NULL)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{output}}{\code{output} ID of \code{\link[shiny:downloadButton]{shiny::downloadButton()}}/\code{\link[shiny:downloadButton]{shiny::downloadLink()}}}

\item{\code{filename}}{File path to save the downloaded file to.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
\verb{$get_download()} will return the final save location of the file. This
location can change depending on the value of \code{filename} and response
headers.

Location logic:
\itemize{
\item If \code{filename} is not NULL, \code{filename} will be returned.
\item If a \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition}{\code{content-disposition} \code{filename}} is provided,
then a temp file containing this \code{filename} will be
returned.
\item Otherwise, a temp file ending in \code{.download} will be returned.
}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/10_download", package = "shiny")
app <- AppDriver$new(app_path)

# Get rock.csv as a tempfile
app$get_download("downloadData")
#> [1] "/TEMP/PATH/rock.csv"

# Get rock.csv as a "./myfile.csv"
app$get_download("downloadData", filename = "./myfile.csv")
#> [1] "./myfile.csv"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_text"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_text}{}}}
\subsection{Method \code{expect_text()}}{
Expect snapshot of UI text

\verb{$expect_text()} will extract the text value of all matching elements via
\code{TAG.textContent} and store them in a snapshot file. This method is more
robust to internal package change as only the text values will be
maintained. Note, this method will not retrieve any \verb{<input />} value's
text content, e.g. text inputs or text areas, as the input values are not
stored in the live HTML.

When possible, use \verb{$expect_text()} over \verb{$expect_html()} to allow
package authors room to alter their HTML structures. The resulting array
of \code{TAG.textContent} values found will be stored in a snapshot file.

Please see \href{https://rstudio.github.io/shinytest2/articles/robust.html}{Robust testing} for more details.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_text(selector, ..., cran = deprecated())}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{selector}}{A DOM CSS selector to be passed into \code{document.querySelectorAll()}}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
hello_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(hello_app)

# Make a snapshot of `"Hello Shiny!"`
app$expect_text("h2")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_text"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_text}{}}}
\subsection{Method \code{get_text()}}{
Get UI text

\verb{$get_text()} will extract the text value of all matching elements via
\code{TAG.textContent}. Note, this method will not retrieve any \verb{<input />}
value's text content, e.g. text inputs or text areas, as the input values
are not stored in the live HTML.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_text(selector)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{selector}}{A DOM CSS selector to be passed into \code{document.querySelectorAll()}}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
A vector of character values
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
hello_app <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(hello_app)

app$get_text("h2")
#> [1] "Hello Shiny!"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_html"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_html}{}}}
\subsection{Method \code{expect_html()}}{
Expect snapshot of UI HTML

\verb{$expect_html()} will extract the full DOM structures of each matching
element and store them in a snapshot file. This method captures internal
DOM structure which may be brittle to changes by external authors or
dependencies.

Note, this method will not retrieve any \verb{<input />} value's
text content, e.g. text inputs or text areas, as the input values are not
stored in the live HTML.

When possible, use \verb{$expect_text()} over \verb{$expect_html()} to allow
package authors room to alter their HTML structures. The resulting array
of \code{TAG.textContent} values found will be stored in a snapshot file.

Please see \href{https://rstudio.github.io/shinytest2/articles/robust.html}{Robust testing} for more details.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_html(selector, ..., outer_html = TRUE, cran = deprecated())}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{selector}}{A DOM selector to be passed into \code{document.querySelectorAll()}}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{outer_html}}{If \code{TRUE} (default), the full DOM structure will be returned (\code{TAG.outerHTML}).
If \code{FALSE}, the full DOM structure of the child elements will be returned (\code{TAG.innerHTML}).}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/04_mpg", package = "shiny")
app <- AppDriver$new(app_path)

# Save a snapshot of the `caption` output
app$expect_html("#caption")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_html"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_html}{}}}
\subsection{Method \code{get_html()}}{
Get UI HTML

\verb{$get()} will extract the full DOM structures of each matching
element. This method captures internal
DOM structure which may be brittle to changes by external authors or
dependencies.

Note, this method will not retrieve any \verb{<input />} value's
text content, e.g. text inputs or text areas, as the input values are not
stored in the live HTML.

Please see \href{https://rstudio.github.io/shinytest2/articles/robust.html}{Robust testing} for more details.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_html(selector, ..., outer_html = TRUE)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{selector}}{A DOM selector to be passed into \code{document.querySelectorAll()}}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{outer_html}}{If \code{TRUE}, the full DOM structure will be returned (\code{TAG.outerHTML}).
If \code{FALSE}, the full DOM structure of the child elements will be returned (\code{TAG.innerHTML}).}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/03_reactivity", package = "shiny")
app <- AppDriver$new(app_path, check_names = FALSE)

app$set_inputs(caption = "Custom value!")
cat(app$get_html(".shiny-input-container")[1])
#> <div class="form-group shiny-input-container">
#>   <label class="control-label" id="caption-label" for="caption">Caption:</label>
#>   <input id="caption" type="text" class="form-control shiny-bound-input" value="Data Summary">
#> </div>
## ^^ No update to the DOM of `caption`
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_js"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_js}{}}}
\subsection{Method \code{expect_js()}}{
Expect snapshot of JavaScript script output

This is a building block function that may be called by other functions.
For example, \verb{$expect_text()} and \verb{$expect_html()} are thin wrappers around this function.

Once the \code{script} has executed, the JSON result will be saved to a snapshot file.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_js(
  script = missing_arg(),
  ...,
  file = missing_arg(),
  timeout = missing_arg(),
  pre_snapshot = NULL,
  cran = deprecated()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{script}}{A string containing the JavaScript script to be executed.}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{file}}{A file containing JavaScript code to be read and used as the \code{script}. Only one of \code{script} or \code{file} can be specified.}

\item{\code{timeout}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}

\item{\code{pre_snapshot}}{A function to be called on the result of the script before taking the snapshot.
\verb{$expect_html()} and \verb{$expect_text()} both use \code{\link[=unlist]{unlist()}}.}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/07_widgets", package = "shiny")
app <- AppDriver$new(app_path)

# Track how many clicks are given to `#update` button
app$run_js("
  window.test_counter = 0;
  $('#update').click(() => window.test_counter++);
")
app$set_inputs(obs = 20)
# Click the update button, incrementing the counter
app$click("update")
# Save a snapshot of number of clicks (1)
app$expect_js("window.test_counter;")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_js"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_js}{}}}
\subsection{Method \code{get_js()}}{
Execute JavaScript code in the browser and return the result

This function will block the local R session until the code has finished
executing its \emph{tick} in the browser. If a \code{Promise} is returned from the
script, \verb{$get_js()} will wait for the promise to resolve. To have
JavaScript code execute asynchronously, wrap the code in a Promise object
and have the script return an atomic value.

Arguments will have to be inserted into the script as there is not access
to \code{arguments}. This can be done with commands like \code{paste()}. If using
\code{glue::glue()}, be sure to use uncommon \code{.open} and \code{.close} values to
avoid having to double all \verb{\{} and \verb{\}}.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_js(
  script = missing_arg(),
  ...,
  file = missing_arg(),
  timeout = missing_arg()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{script}}{JavaScript to execute. If a JavaScript Promise is returned,
the R session will block until the promise has been resolved and return
the value.}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{file}}{A (local) file containing JavaScript code to be read and used
as the \code{script}. Only one of \code{script} or \code{file} can be specified.}

\item{\code{timeout}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
Result of the \code{script} (or \code{file} contents)
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
library(shiny)
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Execute JavaScript code in the app's browser
app$get_js("1 + 1;")
#> [1] 2

# Execute a JavaScript Promise. Return the resolved value.
app$get_js("
  new Promise((resolve) => {
    setTimeout(() => resolve(1 + 1), 1000)
  }).
  then((value) => value + 1);
")
#> [1] 3

# With escaped arguments
loc_field <- "hostname"
js_txt <- paste0("window.location[", jsonlite::toJSON(loc_field, auto_unbox = TRUE), "]")
app$get_js(js_txt)
#> [1] "127.0.0.1"

# With `glue::glue()`
js_txt <- glue::glue_data(
  lapply(
    list(x = 40, y = 2),
    jsonlite::toJSON,
    auto_unbox = TRUE
  ),
  .open = "<", .close = ">",
  "let answer = function(a, b) {\n",
  "  return a + b;\n",
  "};\n",
  "answer(<x>, <y>);\n"
)
app$get_js(js_txt)
#> [1] 42
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-run_js"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-run_js}{}}}
\subsection{Method \code{run_js()}}{
Execute JavaScript code in the browser

This function will block the local R session until the code has finished
executing its \emph{tick} in the browser.

The final result of the code will be ignored and not returned to the R session.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$run_js(
  script = missing_arg(),
  ...,
  file = missing_arg(),
  timeout = missing_arg()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{script}}{JavaScript to execute.}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{file}}{A (local) file containing JavaScript code to be read and used
as the \code{script}. Only one of \code{script} or \code{file} can be specified.}

\item{\code{timeout}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
library(shiny)
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Get JavaScript answer from the app's browser
app$get_js("1 + 1")
#> [1] 2
# Execute JavaScript code in the app's browser
app$run_js("1 + 1")
# (Returns `app` invisibly)

# With escaped arguments
loc_field <- "hostname"
js_txt <- paste0("window.location[", jsonlite::toJSON(loc_field, auto_unbox = TRUE), "]")
app$run_js(js_txt)
app$get_js(js_txt)
#> [1] "127.0.0.1"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_screenshot"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_screenshot}{}}}
\subsection{Method \code{expect_screenshot()}}{
Expect a screenshot of the Shiny application

This method takes a screenshot of the application (of only the \code{selector}
area) and compares the image to the expected image.

Please be aware that this method is very brittle to changes outside of your Shiny application.
These changes can include:
\itemize{
\item running on a different R version
\item running on a different in operating system
\item using a different default system font
\item using different package versions
These differences are explicitly clear when working with plots.
}

Unless absolutely necessary for application consistency, it is strongly
recommended to use other expectation methods.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_screenshot(
  ...,
  threshold = getOption("shinytest2.compare_screenshot.threshold", NULL),
  kernel_size = getOption("shinytest2.compare_screenshot.kernel_size", 5),
  screenshot_args = missing_arg(),
  delay = missing_arg(),
  selector = missing_arg(),
  compare = missing_arg(),
  quiet = FALSE,
  name = NULL,
  cran = deprecated()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{threshold}}{Parameter supplied to \code{\link[=compare_screenshot_threshold]{compare_screenshot_threshold()}}
when using the default \code{compare} method. The default value can be set
globally with the \code{shinytest2.compare_screenshot.threshold} option.

If the value of \code{threshold} is \code{NULL},
\code{\link[=compare_screenshot_threshold]{compare_screenshot_threshold()}} will act like
\code{\link[testthat:expect_snapshot_file]{testthat::compare_file_binary}}. However, if \code{threshold} is a
positive number, it will be compared against the largest convolution
value found if the two images fail a \code{\link[testthat:expect_snapshot_file]{testthat::compare_file_binary}}
comparison.

Which value should I use? Threshold values values below 5 help deter
false-positive screenshot comparisons (such as inconsistent rounded
corners). Larger values in the 10s and 100s will help find \emph{real}
changes. However, not all values are one size fits all and you will
need to play with a threshold that fits your needs.}

\item{\code{kernel_size}}{Parameter supplied to
\code{\link[=compare_screenshot_threshold]{compare_screenshot_threshold()}} when using the default \code{compare}
method. The \code{kernel_size} represents the height and width of the
convolution kernel applied to the pixel differences. This integer-like
value should be relatively small. The default value can be set globally
with the \code{shinytest2.compare_screenshot.kernel_size} option.}

\item{\code{screenshot_args}}{This named list of arguments is passed along to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method. If missing, the
value will default to \verb{$new(screenshot_args=)}.

If \code{screenshot_args} is:
\itemize{
\item \code{TRUE}: A screenshot of the browser's scrollable area will be taken
with no delay
\item A named list of arguments: Arguments passed directly to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method. The
\code{delay} argument will default to \code{0} seconds. The \code{selector} argument
can take two special values in addition to being a CSS DOM selector.
\itemize{
\item \code{"scrollable_area"} (default): The entire scrollable area will be
captured. Typically this is your browser's viewport size, but it
can be larger if the page is scrollable. This value works well with
Apps that contain elements whose calculated dimensions may be
different than their presented size.
\item \code{"viewport"}: This value will capture the browser's viewport in its
current viewing location, height, and width. It will only capture
what is currently being seen with \verb{$view()}.
}

In \code{v0.3.0}, the default \code{selector} value was changed from the HTML
DOM selector (\code{"html"}) to entire scrollable area
(\code{"scrollable_area"}).
}}

\item{\code{delay}}{The number of \strong{seconds} to wait before taking the screenshot.
This value can be supplied as \code{delay} or \code{screenshot_args$delay}, with the
\code{delay} parameter having preference.}

\item{\code{selector}}{The selector is a CSS selector that will be used to select a
portion of the page to be captured. This value can be supplied as
\code{selector} or \code{screenshot_args$selector}, with the \code{selector} parameter
having preference.

In \code{v0.3.0}, two special \code{selector} values were added:
\itemize{
\item \code{"scrollable_area"} (default): The entire scrollable area will be
captured. Typically this is your browser's viewport size, but it can be
larger if the page is scrollable. This value works well with Apps that
contain elements whose calculated dimensions may be different than their
presented size.
\item \code{"viewport"}: This value will capture the browser's viewport in its
current viewing location, height, and width. It will only capture
what is currently being seen with \verb{$view()}.
}

In \code{v0.3.0}, the default \code{selector} value was changed from the HTML DOM
selector (\code{"html"}) to entire scrollable area (\code{"scrollable_area"}).}

\item{\code{compare}}{A function used to compare the screenshot snapshot files.
The function should take two inputs, the paths to the \code{old} and \code{new}
snapshot, and return either \code{TRUE} or \code{FALSE}.

\code{compare} defaults to a function that wraps around
\code{compare_screenshot_threshold(old, new, threshold = threshold, kernel_size = kernel_size, quiet = quiet)}. Note: if \code{threshold} is
\code{NULL} (default), \code{compare} will behave as if
\code{\link[testthat:expect_snapshot_file]{testthat::compare_file_binary()}} was provided, comparing the two
images byte-by-byte.}

\item{\code{quiet}}{Parameter supplied to \code{\link[=compare_screenshot_threshold]{compare_screenshot_threshold()}}
when using the default \code{compare} method. If \code{FALSE}, diagnostic
information will be presented when the computed value is larger than a
non-\code{NULL} \code{threshold} value.}

\item{\code{name}}{The file name to be used for the snapshot. The file extension
will overwritten to \code{.png}. By default, the \code{name} supplied to
\code{app} on initialization with a counter will be used (e.g. \code{"NAME-001.png"}).}

\item{\code{cran}}{Deprecated. With \code{AppDriver} never testing on CRAN,
this parameter no longer has any effect.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
# These example lines should be performed in a `./tests/testthat`
# test file so that snapshot files can be properly saved

app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path, variant = platform_variant())

# Expect a full size screenshot to be pixel perfect
app$expect_screenshot()

# Images are brittle when containing plots
app$expect_screenshot(selector = "#distPlot")

# Test with more threshold in pixel value differences
# Helps with rounded corners
app$expect_screenshot(threshold = 10)

# Equivalent expectations
app$expect_screenshot() # default
app$expect_screenshot(threshold = NULL)
app$expect_screenshot(compare = testthat::compare_file_binary)
expect_snapshot_file(
  app$get_screenshot(),
  variant = app$get_variant(),
  compare = testthat::compare_file_binary
)

# Equivalent expectations
app$expect_screenshot(threshold = 3, kernel_size = 5)
app$expect_screenshot(compare = function(old, new) {
  compare_screenshot_threshold(
    old, new,
    threshold = 3,
    kernel_size = 5
  )
})
expect_screenshot_file(
  app$get_screenshot(),
  variant = app$get_variant(),
  compare = function(old, new) {
    compare_screenshot_threshold(
      old, new,
      threshold = 3,
      kernel_size = 5
    )
  }
)

# Take a screenshot of the entire scrollable area
app$expect_screenshot()
app$expect_screenshot(selector = "scrollable_area")

## Take a screenshot of the current viewport
# Shrink the window to be smaller than the app
app$set_window_size(400, 500)
# Scroll the viewport just a bit
app$run_js("window.scroll(30, 70)")
# Take screenshot of browser viewport
app$expect_screenshot(selector = "viewport")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_screenshot"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_screenshot}{}}}
\subsection{Method \code{get_screenshot()}}{
Take a screenshot

Take a screenshot of the Shiny application.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_screenshot(
  file = NULL,
  ...,
  screenshot_args = missing_arg(),
  delay = missing_arg(),
  selector = missing_arg()
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{file}}{If \code{NULL}, then the image will be displayed to the current
Graphics Device. If a file path, then the screenshot will be saved to
that file.}

\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{screenshot_args}}{This named list of arguments is passed along to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method. If missing, the
value will default to \verb{$new(screenshot_args=)}.

If \code{screenshot_args} is:
\itemize{
\item \code{TRUE}: A screenshot of the browser's scrollable area will be taken with no delay
\item A named list of arguments: Arguments passed directly to
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}}'s \verb{$get_screenshot()} method. The \code{delay}
argument will default to \code{0} seconds. The \code{selector} argument can take two
special values in addition to being a CSS DOM selector.
\itemize{
\item \code{"scrollable_area"} (default): The entire scrollable area will be
captured. Typically this is your browser's viewport size, but it can be
larger if the page is scrollable. This value works well with Apps that
contain elements whose calculated dimensions may be different than
their presented size.
\item \code{"viewport"}: This value will capture the browser's viewport in its
current viewing location, height, and width. It will only capture
what is currently being seen with \verb{$view()}.
}

In \code{v0.3.0}, the default \code{selector} value was changed from the HTML DOM
selector (\code{"html"}) to entire scrollable area (\code{"scrollable_area"}).
}

If \code{screenshot_args=FALSE} is provided, the parameter will be ignored and a
screenshot will be taken with default behavior.}

\item{\code{delay}}{The number of \strong{seconds} to wait before taking the screenshot.
This value can be supplied as \code{delay} or \code{screenshot_args$delay}, with the
\code{delay} parameter having preference.}

\item{\code{selector}}{The selector is a CSS selector that will be used to select a
portion of the page to be captured. This value can be supplied as
\code{selector} or \code{screenshot_args$selector}, with the \code{selector} parameter
having preference.

In \code{v0.3.0}, two special \code{selector} values were added:
\itemize{
\item \code{"scrollable_area"} (default): The entire scrollable area will be
captured. Typically this is your browser's viewport size, but it can be
larger if the page is scrollable. This value works well with Apps that
contain elements whose calculated dimensions may be different than their
presented size.
\item \code{"viewport"}: This value will capture the browser's viewport in its
current viewing location, height, and width. It will only capture
what is currently being seen with \verb{$view()}.
}

In \code{v0.3.0}, the default \code{selector} value was changed from the HTML DOM
selector (\code{"html"}) to entire scrollable area (\code{"scrollable_area"}).}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

# Display in graphics device
app$get_screenshot()

# Update bins then display `"disPlot"` in graphics device
app$set_inputs(bins = 10)
app$get_screenshot(selector = "#distPlot")

# Save screenshot to file and view it
tmpfile <- tempfile(fileext = ".png")
app$get_screenshot(tmpfile)
showimage::show_image(tmpfile)
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-wait_for_idle"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-wait_for_idle}{}}}
\subsection{Method \code{wait_for_idle()}}{
Wait for Shiny to not be busy (idle) for a set amount of time

Waits until Shiny has not been busy for a set duration of time, e.g. no
reactivity is updating or has occurred.

This is useful, for example, when waiting for your application to
initialize or if you've resized the window with \verb{$set_window_size()} and
want to make sure all plot redrawing is complete before take a
screenshot.

By default,
\itemize{
\item \verb{$new(wait = TRUE)} waits for Shiny to not be busy after initializing
the application
\item \verb{$set_window_size(wait = TRUE)} waits for Shiny to not be busy after
resizing the window.)
}
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$wait_for_idle(duration = 500, timeout = missing_arg())}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{duration}}{How long Shiny must be idle (in ms) before unblocking the
R session.}

\item{\code{timeout}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
\code{invisible(self)} if Shiny stabilizes within the \code{timeout}.
Otherwise an error will be thrown
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

pre_value <- app$get_value(output = "distPlot")
# Update bins value
app$set_inputs(bins = 10, wait_ = FALSE)
middle_value <- app$get_value(output = "distPlot")
app$wait_for_idle()
post_value <- app$get_value(output = "distPlot")

# No guarantee that these values are different
identical(pre_value, middle_value)
# Will not be equal
identical(pre_value, post_value)

# ---------------------
## Change the screen size to trigger a plot update
pre_value <- app$get_value(output = "distPlot")
app$set_window_size(height = 1080, width = 1920, wait = FALSE)
middle_value <- app$get_value(output = "distPlot")
app$wait_for_idle()
post_value <- app$get_value(output = "distPlot")

# No guarantee that these values are different
identical(pre_value, middle_value)
# Will not be equal
identical(pre_value, post_value)
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-wait_for_value"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-wait_for_value}{}}}
\subsection{Method \code{wait_for_value()}}{
Wait for a new Shiny value

Waits until an \code{input}, \code{output}, or \code{export} Shiny value is not one of
\code{ignore}d values, or the \code{timeout} is reached.

Only a single \code{input}, \code{output}, or \code{export} value may be used.

This function can be useful in helping determine if an application
has finished processing a complex reactive situation.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$wait_for_value(
  ...,
  input = missing_arg(),
  output = missing_arg(),
  export = missing_arg(),
  ignore = list(NULL, ""),
  timeout = missing_arg(),
  interval = 400
)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{Must be empty. Allows for parameter expansion.}

\item{\code{input, output, export}}{A name of an input, output, or export value.
Only one of these parameters may be used.}

\item{\code{ignore}}{List of possible values to ignore when checking for
updates.}

\item{\code{timeout}}{Amount of time to wait before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}

\item{\code{interval}}{How often to check for the condition, in ms.}

\item{\code{timeout_}}{Amount of time to wait for a new \code{output} value before giving up (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
Newly found value
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
library(shiny)
shiny_app <- shinyApp(
  fluidPage(
    h1("Dynamic output"),
    actionButton("display", "Display UI"),
    uiOutput("dym1"),
  ),
  function(input, output) {
    output$dym1 <- renderUI({
      req(input$display)
      Sys.sleep(runif(1, max = 2)) # Artificial calculations
      tagList(
        sliderInput("slider1", "Slider #1", 0, 100, 25),
        uiOutput("dym2")
      )
    })
    output$dym2 <- renderUI({
      Sys.sleep(runif(1, max = 2)) # Artificial calculations
      tagList(
        sliderInput("slider2", "Slider #2", 0, 100, 50),
        "Total:", verbatimTextOutput("total")
      )
    })
    output$total <- renderText({
      req(input$slider1, input$slider2)
      input$slider1 + input$slider2
    })
  }
)

app <- AppDriver$new(shiny_app)

# Create UI / output values
app$click("display")
# Wait for total to be calculated (or have a non-NULL value)
new_total_value <- app$wait_for_value(output = "total")
#> [1] "75"
app$get_value(output = "total")
#> [1] "75"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-wait_for_js"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-wait_for_js}{}}}
\subsection{Method \code{wait_for_js()}}{
Wait for a JavaScript expression to be true

Waits until a JavaScript \code{expr}ession evaluates to \code{true} or the
\code{timeout} is exceeded.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$wait_for_js(script, timeout = missing_arg(), interval = 100)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{script}}{A string containing JavaScript code. This code must
eventually return a \href{https://developer.mozilla.org/en-US/docs/Glossary/Truthy}{\code{true}thy value} or a
timeout error will be thrown.}

\item{\code{timeout}}{How long the script has to return a \code{true}thy value (milliseconds).
Defaults to the resolved \code{timeout} value during the \code{AppDriver} initialization.}

\item{\code{interval}}{How often to check for the condition (milliseconds).}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
\code{invisible(self)} if expression evaluates to \code{true} without error
within the timeout. Otherwise an error will be thrown
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
shiny_app <- shinyApp(h1("Empty App"), function(input, output) { })
app <- AppDriver$new(shiny_app)

# Contrived example:
# Wait until `Date.now()` returns a number that ends in a 5. (0 - 10 seconds)
system.time(
  app$wait_for_js("Math.floor((Date.now() / 1000) \% 10) == 5;")
)

## A second example where we run the contents of a JavaScript file
## and use the result to wait for a condition
app$run_js(file = "complicated_file.js")
app$wait_for_js("complicated_condition();")
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-expect_unique_names"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-expect_unique_names}{}}}
\subsection{Method \code{expect_unique_names()}}{
Expect unique input and output names.

If the HTML has duplicate input or output elements with matching \code{id}
values, this function will throw an error. It is similar to
\code{AppDriver$new(check_names = TRUE)}, but asserts that no warnings are
displayed.

This method will not throw if a single input and a single output have the
same name.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$expect_unique_names()}\if{html}{\out{</div>}}
}

\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
shiny_app <- shinyApp(
  ui = fluidPage(
    # Duplicate input IDs: `"text"`
    textInput("text", "Text 1"),
    textInput("text", "Text 2")
  ),
  server = function(input, output) {
    # empty
  }
)
# Initial checking for unique names (default behavior)
app <- AppDriver$new(shiny_app, check_names = TRUE)
#> Warning:
#> ! Shiny inputs should have unique HTML id values.
#> i The following HTML id values are not unique:
#> • text
app$stop()

# Manually assert that all names are unique
app <- AppDriver$new(shiny_app, check_names = FALSE)
app$expect_unique_names()
#> Error: `app_check_unique_names(self, private)` threw an unexpected warning.
#> Message: ! Shiny inputs should have unique HTML id values.
#> i The following HTML id values are not unique:
#>   • text
#> Class:   rlang_warning/warning/condition
app$stop()
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_dir"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_dir}{}}}
\subsection{Method \code{get_dir()}}{
Retrieve the Shiny app path
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_dir()}\if{html}{\out{</div>}}
}

\subsection{Returns}{
The directory containing the Shiny application or Shiny runtime
document. If a URL was provided to \code{app_dir} during initialization, the
current directory will be returned.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

identical(app$get_dir(), app_path)
#> [1] TRUE
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_url"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_url}{}}}
\subsection{Method \code{get_url()}}{
Retrieve the Shiny app URL
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_url()}\if{html}{\out{</div>}}
}

\subsection{Returns}{
URL where the Shiny app is being hosted
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

browseURL(app$get_url())
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_window_size"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_window_size}{}}}
\subsection{Method \code{get_window_size()}}{
Get window size

Get current size of the browser window, as list of numeric scalars
named \code{width} and \code{height}.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_window_size()}\if{html}{\out{</div>}}
}

\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

app$get_window_size()
#> $width
#> [1] 992
#>
#> $height
#> [1] 1323
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-set_window_size"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-set_window_size}{}}}
\subsection{Method \code{set_window_size()}}{
Sets size of the browser window.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$set_window_size(width, height, wait = TRUE)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{width, height}}{Height and width of browser, in pixels.}

\item{\code{wait}}{If \code{TRUE}, \verb{$wait_for_idle()} will be called after setting
the window size. This will block until any width specific items (such
as plots) that need to be re-rendered.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
# Set init window size
app <- AppDriver$new(app_path, height = 1400, width = 1000)

app$get_window_size()
#> $width
#> [1] 1000
#>
#> $height
#> [1] 1400

# Manually set the window size
app$set_window_size(height = 1080, width = 1920)
app$get_window_size()
#> $width
#> [1] 1920
#>
#> $height
#> [1] 1080
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_chromote_session"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_chromote_session}{}}}
\subsection{Method \code{get_chromote_session()}}{
Get Chromote Session

Get the \code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}} object from the \pkg{chromote} package.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_chromote_session()}\if{html}{\out{</div>}}
}

\subsection{Returns}{
\code{\link[chromote:ChromoteSession]{chromote::ChromoteSession}} R6 object
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

b <- app$get_chromote_session()
b$Runtime$evaluate("1 + 1")
#> $result
#> $result$type
#> [1] "number"
#>
#> $result$value
#> [1] 2
#>
#> $result$description
#> [1] "2"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_variant"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_variant}{}}}
\subsection{Method \code{get_variant()}}{
Get the variant

Get the \code{variant} supplied during initialization
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_variant()}\if{html}{\out{</div>}}
}

\subsection{Returns}{
The \code{variant} value supplied during initialization or \code{NULL} if
no value was supplied.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")

app <- AppDriver$new(app_path)

app$get_variant()
#> NULL

app <- AppDriver$new(app_path, variant = platform_variant())
app$get_variant()
#> [1] "mac-4.1"
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-get_logs"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-get_logs}{}}}
\subsection{Method \code{get_logs()}}{
Get all logs

Retrieve all of the debug logs that have been recorded.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$get_logs()}\if{html}{\out{</div>}}
}

\subsection{Returns}{
A data.frame with the following columns:
\itemize{
\item \code{workerid}: The shiny worker ID found within the browser
\item \code{timestamp}: POSIXct timestamp of the message
\item \code{location}: The location of the message was found. One of three values:
\itemize{
\item \code{"shinytest2"}: Occurs when \verb{$log_message()} is called
\item \code{"shiny"}: \code{stdin} and \code{stdout} messages from the Shiny server. Note \code{message()} output is sent to \code{stdout}.
\item \code{"chromote"}: Captured by the \pkg{chromote} event handlers. See
\href{https://chromedevtools.github.io/devtools-protocol/1-3/Runtime/#event-consoleAPICalled}{console API},
\href{https://chromedevtools.github.io/devtools-protocol/1-3/Runtime/#event-exceptionThrown}{exception thrown},
\href{https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-webSocketFrameSent}{websocket sent}, and
\href{https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-webSocketFrameReceived}{websocket received}
for more details
}
\item \code{level}: For a given location, there are different types of log levels.
\itemize{
\item \code{"shinytest2"}: \code{"log"}; Only log messages are captured.
\item \code{"shiny"}: \code{"stdout"} or \code{"stderr"}; Note, \code{message()} output is sent
to \code{stderr}.
\item \code{"chromote"}: Correspond to any level of a JavaScript
\code{console.LEVEL()} function call. Typically, these are "log"\code{and}"error"\verb{but can include}"info"\verb{, }"debug"\verb{, and }"warn"\verb{. If }options(shiny.trace = TRUE)\verb{, then the level will recorded as }"websocket"`.
}
}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app1 <- AppDriver$new(system.file("examples/01_hello", package = "shiny"))

app1$get_logs()
#> \{shinytest2\} R  info   10:00:28.86 Start AppDriver initialization
#> \{shinytest2\} R  info   10:00:28.86 Starting Shiny app
#> \{shinytest2\} R  info   10:00:29.76 Creating new ChromoteSession
#> \{shinytest2\} R  info   10:00:30.56 Navigating to Shiny app
#> \{shinytest2\} R  info   10:00:30.70 Injecting shiny-tracer.js
#> \{chromote\}   JS info   10:00:30.75 shinytest2; jQuery found
#> \{chromote\}   JS info   10:00:30.77 shinytest2; Waiting for shiny session to connect
#> \{chromote\}   JS info   10:00:30.77 shinytest2; Loaded
#> \{shinytest2\} R  info   10:00:30.77 Waiting for Shiny to become ready
#> \{chromote\}   JS info   10:00:30.90 shinytest2; Connected
#> \{chromote\}   JS info   10:00:30.95 shinytest2; shiny:busy
#> \{shinytest2\} R  info   10:00:30.98 Waiting for Shiny to become idle for 200ms within 15000ms
#> \{chromote\}   JS info   10:00:30.98 shinytest2; Waiting for Shiny to be stable
#> \{chromote\}   JS info   10:00:31.37 shinytest2; shiny:idle
#> \{chromote\}   JS info   10:00:31.38 shinytest2; shiny:value distPlot
#> \{chromote\}   JS info   10:00:31.57 shinytest2; Shiny has been idle for 200ms
#> \{shinytest2\} R  info   10:00:31.57 Shiny app started
#> \{shiny\}      R  stderr ----------- Loading required package: shiny
#> \{shiny\}      R  stderr ----------- Running application in test mode.
#> \{shiny\}      R  stderr -----------
#> \{shiny\}      R  stderr ----------- Listening on http://127.0.0.1:4679


# To capture all websocket traffic, set `options = list(shiny.trace = TRUE)`
app2 <- AppDriver$new(
  system.file("examples/01_hello", package = "shiny"),
  options = list(shiny.trace = TRUE)
)

app2$get_logs()
## (All WebSocket messages have been replaced with `WEBSOCKET_MSG` in example below)
#> \{shinytest2\} R  info      10:01:57.49 Start AppDriver initialization
#> \{shinytest2\} R  info      10:01:57.50 Starting Shiny app
#> \{shinytest2\} R  info      10:01:58.20 Creating new ChromoteSession
#> \{shinytest2\} R  info      10:01:58.35 Navigating to Shiny app
#> \{shinytest2\} R  info      10:01:58.47 Injecting shiny-tracer.js
#> \{chromote\}   JS info      10:01:58.49 shinytest2; jQuery not found
#> \{chromote\}   JS info      10:01:58.49 shinytest2; Loaded
#> \{shinytest2\} R  info      10:01:58.50 Waiting for Shiny to become ready
#> \{chromote\}   JS info      10:01:58.55 shinytest2; jQuery found
#> \{chromote\}   JS info      10:01:58.55 shinytest2; Waiting for shiny session to connect
#> \{chromote\}   JS websocket 10:01:58.64 send WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.67 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.67 shinytest2; Connected
#> \{chromote\}   JS websocket 10:01:58.71 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.72 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.72 shinytest2; shiny:busy
#> \{chromote\}   JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{shinytest2\} R  info      10:01:58.75 Waiting for Shiny to become idle for 200ms within 15000ms
#> \{chromote\}   JS info      10:01:58.75 shinytest2; Waiting for Shiny to be stable
#> \{chromote\}   JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\}   JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.81 shinytest2; shiny:idle
#> \{chromote\}   JS websocket 10:01:58.82 recv WEBSOCKET_MSG
#> \{chromote\}   JS info      10:01:58.82 shinytest2; shiny:value distPlot
#> \{chromote\}   JS info      10:01:59.01 shinytest2; Shiny has been idle for 200ms
#> \{shinytest2\} R  info      10:01:59.01 Shiny app started
#> \{shiny\}      R  stderr    ----------- Loading required package: shiny
#> \{shiny\}      R  stderr    ----------- Running application in test mode.
#> \{shiny\}      R  stderr    -----------
#> \{shiny\}      R  stderr    ----------- Listening on http://127.0.0.1:4560
#> \{shiny\}      R  stderr    ----------- SEND \{"config":\{"workerId":"","sessionId"|truncated
#> \{shiny\}      R  stderr    ----------- RECV \{"method":"init","data":\{"bins":30,|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"custom":\{"showcase-src":\{"srcref":|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"busy":"busy"\}
#> \{shiny\}      R  stderr    ----------- SEND \{"custom":\{"showcase-src":\{"srcref":|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"recalculating":\{"name":"distPlot",|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"recalculating":\{"name":"distPlot",|truncated
#> \{shiny\}      R  stderr    ----------- SEND \{"busy":"idle"\}
#> \{shiny\}      R  stderr    ----------- SEND \{"errors":\{\},"values":\{"distPlot":|truncated

# The log that is returned is a `data.frame()`.
log <- app2$get_logs()
tibble::glimpse(log)
#> Rows: 43
#> Columns: 5
#> $ workerid  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ timestamp <dttm> 2022-09-19 10:01:57, 2022-09-19 10:01:57, 2022-09-19 10:01:58, 2022…
#> $ location  <chr> "shinytest2", "shinytest2", "shinytest2", "shinytest2", "shinytest2"…
#> $ level     <chr> "info", "info", "info", "info", "info", "info", "info", "info", "inf…
#> $ message   <chr> "Start AppDriver initialization", "Starting Shiny app", "Creating ne…
#> $ workerid  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ timestamp <dttm> 2022-03-16 11:09:57, 2022-03-16 11:09:57, 2022-03-16 11:09:…
#> $ location  <chr> "shinytest2", "shinytest2", "shinytest2", "shinytest2", "shi…
#> $ level     <chr> "info", "info", "info", "info", "info", "info", "info", "inf…
#> $ message   <chr> "Start AppDriver initialization", "Starting Shiny app", "Cre…

# It may be filtered to find desired logs
subset(log, level == "websocket")
## (All WebSocket messages have been replaced with `WEBSOCKET_MSG` in example below)
#> \{chromote\} JS websocket 10:01:58.64 send WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.67 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.71 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.72 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.73 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.81 recv WEBSOCKET_MSG
#> \{chromote\} JS websocket 10:01:58.82 recv WEBSOCKET_MSG
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-log_message"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-log_message}{}}}
\subsection{Method \code{log_message()}}{
Add a message to the \pkg{shinytest2} log.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$log_message(message)}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{message}}{Single message to store in log}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
app_path <- system.file("examples/01_hello", package = "shiny")
app <- AppDriver$new(app_path)

app$log_message("Setting bins to smaller value")
app$set_inputs(bins = 10)
app$get_logs()
}
}
\if{html}{\out{</div>}}

}

}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-AppDriver-stop"></a>}}
\if{latex}{\out{\hypertarget{method-AppDriver-stop}{}}}
\subsection{Method \code{stop()}}{
Stop the Shiny application driver

This method stops all known processes:
\itemize{
\item The Shiny application in the background R process,
\item the background R process hosting the Shiny application, and
\item the Chromote Session instance.
}

To stop your shiny application and return a value from \verb{$stop()}, see
\code{\link[shiny:stopApp]{shiny::stopApp()}}. This is useful in testing to return context
information.

Typically, this can be paired with a button that when clicked will call
\code{shiny::stopApp(info)} to return \code{info} from the test app back to the
main R session.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{AppDriver$stop(signal_timeout = missing_arg())}\if{html}{\out{</div>}}
}

\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{signal_timeout}}{Milliseconds to wait between sending a \code{SIGINT},
\code{SIGTERM}, and \code{SIGKILL} to the Shiny process. Defaults to 500ms and does
not utilize the resolved value from \code{AppDriver$new(timeout=)}. However,
if \pkg{covr} is currently executing, then the \code{timeout} is set to
20,000ms to allow for the coverage report to be generated.}
}
\if{html}{\out{</div>}}
}
\subsection{Returns}{
The result of the background process if the Shiny application has
already been terminated.
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
rlang::check_installed("reactlog")

library(shiny)
shiny_app <- shinyApp(
  ui = fluidPage(
    actionButton("button", "Stop app and return Reactlog"),
    "Click count:", textOutput("count")
  ),
  server = function(input, output) {
    output$count <- renderText({ input$button })
    observe({
      req(input$button)
      stopApp(shiny::reactlog())
    })
  }
)

app <- AppDriver$new(
  shiny_app,
  # Enable reactlog in background R session
  options = list(shiny.reactlog = TRUE)
)

app$click("button")
rlog <- app$stop()
str(head(rlog, 2))
#> List of 2
#> $ :List of 7
#> ..$ action : chr "define"
#> ..$ reactId: chr "r3"
#> ..$ label  : chr "Theme Counter"
#> ..$ type   : chr "reactiveVal"
#> ..$ value  : chr " num 0"
#> ..$ session: chr "bdc7417f2fc8c84fc05c9518e36fdc44"
#> ..$ time   : num 1.65e+09
#> $ :List of 7
#> ..$ action : chr "define"
#> ..$ reactId: chr "r4"
#> ..$ label  : chr "output$count"
#> .. ..- attr(*, "srcref")= int [1:6] 7 32 7 45 32 45
#> .. ..- attr(*, "srcfile")= chr ""
#> ..$ type   : chr "observer"
#> ..$ value  : chr " NULL"
#> ..$ session: chr "bdc7417f2fc8c84fc05c9518e36fdc44"
#> ..$ time   : num 1.65e+09
}
}
\if{html}{\out{</div>}}

}

}
}
