--- title: "Monkey testing" author: David Granjon output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Monkey testing} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(shinytest2) library(shiny) ``` # Monkey (headless) testing with `{shinytest2}` Most people will use `{shinytest2}` with the plug and play `record_test()` app, which is very convenient if you are not familiar with JavaScript. Under the hood, `record_test()` generates an R script composed of a series of directed instructions that manipulates the app to automate testing on CI/CD environments. *Monkey testing*, a type of testing where random inputs are used to test the behavior of an app, is widely used by web developers to check application robustness, particularly in apps with a large number of inputs. The goal is ultimately to try to break the app by triggering unexpected combinations. Most available libraries are JS-based such as [gremlins.js](https://github.com/marmelab/gremlins.js), traditionally combined with JS-based global testing libraries like [Puppeteer](https://pptr.dev/), but can work with `{shinytest2}` as well. In this vignette we'll provide a more thorough overview of the `AppDriver` R6 class (extending on the concepts covered in the [Testing in depth](in-depth.html) article), which allows the developer to programmatically control the app. We'll see how we can seamlessly benefit from gremlins.js with only few lines of code. ## Initialize the driver We consider a simple app composed of a slider and a plot output: ``` r ui <- fluidPage( sliderInput("obs", "Number of observations:", min = 0, max = 1000, value = 500 ), plotOutput("distPlot") ) # Server logic server <- function(input, output) { output$distPlot <- renderPlot({ hist(rnorm(input$obs)) }) } # Complete app with UI and server components shinyApp(ui, server) ``` The driver may be initialized with: ``` r headless_app <- AppDriver$new( app_dir = "", name = "monkey-test", shiny_args = list(port = 3515) ) ``` Note the `shiny_args` slot allowing you to pass custom options to `shiny::runApp()` such as the port, which might be useful if your organization restricts port number. `load_timeout` defaults to 10s and 20s locally and during CI/CD, respectively. Therefore, if your app takes longer to launch, you can change this value. Keep in mind that an app taking more than 20s to launch is generally under-optimized and would require specific care such as profiling and refactoring. AppDriver starts a Chrome-based headless browser. If you need specific [flags](https://peter.sh/experiments/chromium-command-line-switches/) that are not available by default in `{shinytest2}`, you can pass them before instantiating the driver: ``` r chromote::set_chrome_args( c( chromote::default_chrome_args(), # Custom flags: see https://peter.sh/experiments/chromium-command-line-switches/ ) ) ``` Some flags are considered by default, particularly `--no-sandbox`, which is applied only on CI/CD, as Chrome won't start without it. If you run this script locally, you may add `view = TRUE` to open the Chrome Devtools, which will significantly ease the testing calibration. I highly recommend creating the test protocol locally and then moving to CI/CD later when all bugs are fixed. In the below figure, the application is shown on the left side panel. The top-right side panel shows the DOM elements (default) and the bottom-right side panel displays the JavaScript console output. ```{r echo=FALSE, eval=TRUE, out.width='100%', fig.align='center'} knitr::include_graphics("images/gremlins-start.png") ``` ## Injecting gremlins.js The next steps consist of injecting the gremlins.js dependency in the DOM so that we can unleash the horde. ### Easy way The easiest way to inject gremlins.js is to call: ``` r headless_app$run_js(" let s = document.createElement('script'); s.src = 'https://unpkg.com/gremlins.js'; document.body.appendChild(s); ") ``` This creates a `