-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.R
279 lines (243 loc) · 8.54 KB
/
server.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# Store the latest version name in an environment, that will persist until
# the R session is closed.
selenium <- rlang::new_environment()
get_selenium_env <- function() {
utils::getFromNamespace("selenium", ns = "selenium")
}
get_from_env <- function(items) {
sel_env <- get_selenium_env()
env_get(sel_env, items, default = NULL)
}
set_in_env <- function(...) {
sel_env <- get_selenium_env()
env_bind(sel_env, ...)
}
#' Download and start the Selenium server.
#'
#' @description
#' `r lifecycle::badge('experimental')`
#'
#' Downloads the latest release of Selenium Server, and then runs it as a
#' background process. You must have Java installed for this command to work.
#'
#' @param version The version of Selenium Server to download and run. By
#' default, the latest major or minor release is used.
#' @param selenium_manager Whether to enable Selenium Manager, which will
#' automatically download any missing drivers. Defaults to `TRUE`.
#' @param interactive By default, if you don't have a version downloaded, you
#' will be prompted to confirm that you want to download it, and the function
#' will error if [rlang::is_interactive()] returns `FALSE`. To allow this
#' function to work in a non-interactive setting, set this to `FALSE`.
#' @param verbose Passed into [utils::download.file()]. Note that setting this
#' to `FALSE` will *not* disable the prompt if a file needs to be downloaded.
#' @param temp Whether to use a temporary directory to download the Selenium
#' Server `.jar` file. This will ensure that the file is deleted after it is
#' used, but means that you will have to redownload the file with every new
#' R session. If `FALSE`, the file is saved in your user data directory.
#' @param path The path where the downloaded Selenium Server `.jar` file will
#' be saved. Overrides `temp`.
#' @param echo_cmd Passed into [processx::process$new()][processx::process].
#' @param extra_args A character vector of extra arguments to pass into the
#' Selenium Server call. See the list of options here:
#' <https://www.selenium.dev/documentation/grid/configuration/cli_options/>
#'
#' @returns A [processx::process] object. Call `<process>$kill()` to stop the
#' server.
#'
#' @seealso
#' The [package website](https://ashbythorpe.github.io/selenium-r/index.html)
#' for more ways to start the Selenium server.
#'
#' @examples
#' \dontrun{
#' # Disables the prompt that asks you whether you want to download Selenium server
#' server <- selenium_server(interactive = FALSE)
#'
#' # Saves the server in your user data directory
#' server <- selenium_server(temp = FALSE)
#' server$kill()
#'
#' # The server doesn't have to be downloaded again
#' server <- selenium_server(temp = FALSE)
#'
#' # Here we use extra arguments to increase the timeout of client sessions,
#' # allowing sessions to stay open for longer without being automatically
#' # terminated.
#' server <- selenium_server(extra_args = c("--session-timeout", "3000"))
#' }
#'
#' @export
selenium_server <- function(version = "latest",
selenium_manager = TRUE,
interactive = TRUE,
verbose = TRUE,
temp = TRUE,
path = NULL,
echo_cmd = FALSE,
extra_args = c()) {
check_string(version)
check_bool(selenium_manager)
check_bool(interactive)
check_bool(verbose)
check_bool(temp)
check_string(path, allow_null = TRUE)
check_bool(echo_cmd)
check_character(extra_args, allow_null = TRUE)
if (version != "latest") {
n_version <- numeric_version(version)
list_version <- unclass(n_version)
if (list_version[[1]][length(list_version[[1]])] != 0) {
list_version[[1]][length(list_version[[1]])] <- 0
class(list_version) <- class(n_version)
rlang::abort(c(
"`version` must be a major or minor release, not a patch.",
"i" = paste0("Supplied version: ", version),
"i" = paste0("Did you mean: ", list_version, "?")
))
}
if (n_version < "4.9.0" && selenium_manager) {
rlang::warn(c(
"Selenium Server 4.9.0 or higher is required to use Selenium Manager.",
"x" = paste0("Actual version requested: ", n_version, "."),
"x" = "Disabling Selenium Manager.",
"i" = "Set `selenium_manager` to `FALSE` to disable this warning."
))
selenium_manager <- FALSE
}
}
if (version == "latest") {
release_name <- rlang::try_fetch(get_latest_version_name(), error = identity)
if (rlang::is_error(release_name)) {
version <- get_version_from_files(release_name)
release_name <- paste0("selenium-", version)
} else {
version <- gsub("^selenium-", "", release_name)
}
} else {
release_name <- paste0("selenium-", version)
}
file_name <- paste0("selenium-server-", version, ".jar")
if (!is.null(path)) {
dir <- normalizePath(path, winslash = "/", mustWork = TRUE)
} else if (temp) {
dir <- tempfile(pattern = "file", tmpdir = tempdir())
dir.create(dir)
} else {
app_dir <- rappdirs::user_data_dir("selenium-server", "seleniumHQ", version = version)
dir <- normalizePath(app_dir, winslash = "/", mustWork = FALSE)
}
full_path <- if (dir == "") {
NULL
} else {
file.path(dir, file_name)
}
if (interactive && !rlang::is_interactive()) {
rlang::abort(c(
"An interactive session is required to download Selenium Server.",
"Set `interactive` to `FALSE` to disable this warning."
))
}
if ((is.null(full_path) || !file.exists(full_path)) && interactive) {
choices <- utils::menu(
title = paste0("Should we download Selenium Server version ", version, " from GitHub?"),
choices = c("Yes", "No"),
)
if (choices != 1) {
rlang::abort("Selenium Server not found.")
}
}
if (!dir.exists(dir)) {
dir.create(app_dir, recursive = TRUE)
dir <- normalizePath(app_dir, winslash = "/", mustWork = FALSE)
full_path <- file.path(dir, file_name)
}
if (!file.exists(full_path)) {
download_server(full_path, file_name, release_name, verbose)
}
args <- c("-jar", full_path, "standalone")
if (selenium_manager) {
args <- c(args, "--selenium-manager", "true")
}
args <- c(args, extra_args)
processx::process$new(
java_check(),
args = args,
echo_cmd = echo_cmd,
stdout = "|",
stderr = "|",
supervise = TRUE
)
}
download_server <- function(path, file, name, verbose) {
url <- paste0(
"https://github.com/SeleniumHQ/selenium/releases/download/",
name,
"/",
file
)
utils::download.file(url, path, quiet = !verbose)
file
}
get_latest_version_name <- function(page = 1) {
sel_env <- get_selenium_env()
stored_name <- get_from_env("latest_version_name")
if (!is.null(stored_name)) {
return(stored_name)
}
req <- httr2::request("https://api.github.com/repos/seleniumHQ/selenium/tags")
req <- httr2::req_headers(req, "Accept" = "application/vnd.github.v3+json")
token <- if (is_installed("gitcreds")) {
tryCatch(
gitcreds::gitcreds_get(),
error = function(e) NULL
)
} else {
NULL
}
if (!is.null(token)) {
token <- paste("token", token$password)
req <- httr2::req_headers(req, Authorization = token)
}
req <- httr2::req_url_query(req, per_page = 100, page = page)
response <- httr2::req_perform(req)
releases <- httr2::resp_body_json(response)
latest_tag <- find_using(releases, is_nonspecific_release)
if (is.null(latest_tag)) {
get_latest_version_name(page = page + 1)
}
set_in_env(latest_version_name = latest_tag$name)
latest_tag$name
}
get_version_from_files <- function(error) {
dir <- rappdirs::user_data_dir("selenium-server", "seleniumHQ")
dir <- normalizePath(dir, winslash = "/", mustWork = FALSE)
if (!dir.exists(dir)) {
rlang::abort("Could not make request to GitHub.", parent = error)
}
files <- list.dirs(dir, full.names = FALSE)
versions <- numeric_version(files[files != ""])
version <- max((files), na.rm = TRUE)
rlang::warn(c(
"Github request failed: Could not determine latest version of Selenium Server.",
"i" = paste0("Using latest downloaded version ", version, " instead.")
))
version
}
java_check <- function() {
java <- Sys.which("java")
if (identical(unname(java), "")) {
rlang::abort("Java not found. Please install Java to use `selenium_server()`.")
}
java
}
find_using <- function(x, .f) {
for (a in x) {
if (.f(a)) {
return(a)
}
}
NULL
}
is_nonspecific_release <- function(x) {
grepl("^selenium-([0-9]+\\.)+0$", x$name)
}