From 9c0b78f638ed8add291a65b3d4956c17ae7089da Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 9 Jul 2024 08:56:51 -0500 Subject: [PATCH] Wait in click. Closes #405. I tried to make this automatic, and got it working some of the time, but I couldn't find a way to detect that something might change, and thus wait in that situation. I tried to make it as clear as possible for users to be able to fix this. Once a strategy is agreed on, the same strategy should probably be applied to other methods. --- R/live.R | 42 ++++++++++++++++++++++++++---- tests/testthat/html/navigate1.html | 8 ++++++ tests/testthat/html/navigate2.html | 8 ++++++ tests/testthat/test-live.R | 18 +++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 tests/testthat/html/navigate1.html create mode 100644 tests/testthat/html/navigate2.html diff --git a/R/live.R b/R/live.R index 91f4a1b..3f2c73b 100644 --- a/R/live.R +++ b/R/live.R @@ -92,7 +92,7 @@ LiveHTML <- R6::R6Class( self$session$Page$navigate(url, wait_ = FALSE) self$session$wait_for(p) - private$root_id <- self$session$DOM$getDocument(0)$root$nodeId + private$refresh_root() }, #' @description Called when `print()`ed @@ -129,10 +129,21 @@ LiveHTML <- R6::R6Class( #' @description Simulate a click on an HTML element. #' @param css CSS selector or xpath expression. #' @param n_clicks Number of clicks - click = function(css, n_clicks = 1) { + #' @param new_page Whether to wait for a new page to load, such as after + #' clicking a link. + click = function(css, n_clicks = 1, new_page = FALSE) { private$check_active() check_number_whole(n_clicks, min = 1) + # Wait for new page, #405. + if (new_page) { + p <- self$session$Page$loadEventFired(wait_ = FALSE) + on.exit({ + self$session$wait_for(p) + private$refresh_root() + }, add = TRUE) + } + # Implementation based on puppeteer as described in # https://medium.com/@aslushnikov/automating-clicks-in-chromium-a50e7f01d3fb # With code from https://github.com/puppeteer/puppeteer/blob/b53de4e0942e93c/packages/puppeteer-core/src/cdp/Input.ts#L431-L459 @@ -170,6 +181,7 @@ LiveHTML <- R6::R6Class( button = "left" ) } + invisible(self) }, @@ -224,6 +236,7 @@ LiveHTML <- R6::R6Class( deltaX = left, deltaY = top ) + invisible(self) }, @@ -268,14 +281,14 @@ LiveHTML <- R6::R6Class( if (new_chromote && !self$session$is_active()) { suppressMessages({ self$session <- self$session$respawn() - private$root_id <- self$session$DOM$getDocument(0)$root$nodeId + private$refresh_root() }) } }, wait_for_selector = function(css, timeout = 5) { done <- now() + timeout - while(now() < done) { + while (now() < done) { nodes <- private$find_nodes(css) if (length(nodes) > 0) { return(nodes) @@ -289,7 +302,22 @@ LiveHTML <- R6::R6Class( find_nodes = function(css, xpath) { check_exclusive(css, xpath) if (!missing(css)) { - unlist(self$session$DOM$querySelectorAll(private$root_id, css)$nodeIds) + node_ids <- try_fetch( + self$session$DOM$querySelectorAll(private$root_id, css)$nodeIds, + error = function(cnd) { + if (grepl("-32000", cnd_message(cnd))) { + cli::cli_abort( + c( + "Can't find root node.", + i = "Did you issue a {.code click()} without waiting for a {.arg new_page}?" + ), + class = "rvest_error-missing_node", + parent = cnd + ) + } + } + ) + unlist(node_ids) } else { search <- glue::glue(" (function() {{ @@ -324,6 +352,10 @@ LiveHTML <- R6::R6Class( object_id = function(node_id) { # https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-resolveNode self$session$DOM$resolveNode(node_id)$object$objectId + }, + + refresh_root = function() { + private$root_id <- self$session$DOM$getDocument(0)$root$nodeId } ) ) diff --git a/tests/testthat/html/navigate1.html b/tests/testthat/html/navigate1.html new file mode 100644 index 0000000..5778124 --- /dev/null +++ b/tests/testthat/html/navigate1.html @@ -0,0 +1,8 @@ + + + + Navigate 1 + + + Navigate to Page 2 + diff --git a/tests/testthat/html/navigate2.html b/tests/testthat/html/navigate2.html new file mode 100644 index 0000000..5ab020a --- /dev/null +++ b/tests/testthat/html/navigate2.html @@ -0,0 +1,8 @@ + + + + Navigate 2 + + +

Success!

+ diff --git a/tests/testthat/test-live.R b/tests/testthat/test-live.R index e1d7fab..f18be50 100644 --- a/tests/testthat/test-live.R +++ b/tests/testthat/test-live.R @@ -50,6 +50,14 @@ test_that("can click a button", { expect_equal(html_text(html_element(sess, "p")), "double clicked") }) +test_that("can find elements after click that navigates", { + skip_if_no_chromote() + + sess <- read_html_live(html_test_path("navigate1")) + sess$click("a", new_page = TRUE) + expect_equal(html_text2(html_element(sess, "p")), "Success!") +}) + test_that("can scroll in various ways", { skip_if_no_chromote() @@ -88,6 +96,16 @@ test_that("can press special keys",{ expect_equal(html_text(html_element(sess, "#keyInfo")), "]/BracketRight") }) +test_that("gracefully errors on missing root node", { + skip_if_no_chromote() + + sess <- read_html_live(html_test_path("navigate1")) + sess$click("a") + expect_error( + html_element(sess, "p"), + class = "rvest_error-missing_node" + ) +}) # as_key_desc -------------------------------------------------------------